【问题标题】:Play partially downloaded video in swift 3在 swift 3 中播放部分下载的视频
【发布时间】:2018-05-14 01:33:10
【问题描述】:

我需要播放视频的特定部分(例如,从 10 秒到 20 秒)。 所以我使用带有自定义标题范围的Alamofire 来下载视频的这一部分:

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
                }

所以触发了这个错误,我无法播放视频。

我不是视频文件的高手,但我认为视频文件有一个标题,部分下载会破坏这个标题,播放器无法播放。

因此,任何建议或想法(即使是最简单的)都将不胜感激。

tnx 提前。

编辑:视频标题肯定有问题,如果我从视频开始 10 秒开始下载,视频会播放 10 秒,但是结束视频时间或持续时间错误,我该如何解决?

【问题讨论】:

    标签: swift3 alamofire avplayer video-processing video-encoding


    【解决方案1】:

    我不认为 IOS 或 Swift 提供从中间下载图像然后播放的功能。您可以以比特的形式下载视频文件内容,然后可以使用任何文件名的任何扩展名保存它们。 但是,除了您下载的这些字节之外,视频文件还包含大量数据,例如标题、类型、持续时间等。如果您下载的文件不包含这些数据,您的视频将无法播放。

    在这种情况下,我认为(99% 肯定)你将不得不使用 native 代码而不仅仅是 Swift 来解码从特定时间到所需时间的视频。

    有很多免费的库可用,试试看。一种这样的库是ffmpeg。检查this

    一切顺利。

    【讨论】:

    • 这不是一个答案,我已经知道视频文件有一个可以正确播放的信息并且部分下载破坏了它。如您所见,我提供了一个可行的替代方案,因此转换视频也不是一种选择,我只想出于好奇解决 oq。
    • 没有其他选择。我花了很多时间来解决这个问题。
    • 没有什么是不可能的,有问题就有答案。
    • 有一个答案,但您想要一个非常简单的答案,只需提供几行代码,您的视频就会开始播放。但这不会发生。不过,一切顺利。
    【解决方案2】:

    这不是原始问题的答案,但我设法通过使用 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:已添加修复。

    【讨论】:

    • 通过更改 let endCM = CMTime(seconds: startSecond + duration, preferredTimescale: 1) 修复的代码。现在可以从中间下载了。
    猜你喜欢
    • 2017-10-09
    • 1970-01-01
    • 1970-01-01
    • 2023-03-16
    • 2012-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-25
    相关资源
    最近更新 更多