2017-01-25 14 views
2

を凍結する原因となる問題のGIFへのリンクです:はAVPlayerLayerここ

https://gifyu.com/images/ScreenRecording2017-01-25at02.20PM.gif

私は、カメラロールからPHAssetを取って変更可能な組成物にそれを追加して、追加しています追加したトラックを操作してAVAssetExportSessionにエクスポートします。結果はNSTemporaryDirectory()に保存された.movファイル拡張子を持つQuickTimeファイルです:

guard let exporter = AVAssetExportSession(asset: mergedComposition, presetName: AVAssetExportPresetHighestQuality) else { 
     fatalError() 
} 

exporter.outputURL = temporaryUrl 
exporter.outputFileType = AVFileTypeQuickTimeMovie 
exporter.shouldOptimizeForNetworkUse = true 
exporter.videoComposition = videoContainer 

// Export the new video 
delegate?.mergeDidStartExport(session: exporter) 
exporter.exportAsynchronously() { [weak self] in 
    DispatchQueue.main.async { 
     self?.exportDidFinish(session: exporter) 
    } 
} 

私は、このエクスポートしたファイルを取り、いくつかの時間のマッピングに基づいて、クリップに「スローモーション」を適用するマッパーオブジェクトにロードそれに与えられた。ここでの結果はAVCompositionです:

func compose() -> AVComposition { 
    let composition = AVMutableComposition(urlAssetInitializationOptions: [AVURLAssetPreferPreciseDurationAndTimingKey: true]) 

    let emptyTrack = composition.addMutableTrack(withMediaType: AVMediaTypeVideo, preferredTrackID: kCMPersistentTrackID_Invalid) 
    let audioTrack = composition.addMutableTrack(withMediaType: AVMediaTypeAudio, preferredTrackID: kCMPersistentTrackID_Invalid) 

    let asset = AVAsset(url: url) 
    guard let videoAssetTrack = asset.tracks(withMediaType: AVMediaTypeVideo).first else { return composition } 

    var segments: [AVCompositionTrackSegment] = [] 
    for map in timeMappings { 

     let segment = AVCompositionTrackSegment(url: url, trackID: kCMPersistentTrackID_Invalid, sourceTimeRange: map.source, targetTimeRange: map.target) 
     segments.append(segment) 
    } 

    emptyTrack.preferredTransform = videoAssetTrack.preferredTransform 
    emptyTrack.segments = segments 

    if let _ = asset.tracks(withMediaType: AVMediaTypeVideo).first { 
     audioTrack.segments = segments 
    } 

    return composition.copy() as! AVComposition 
} 

そこで私は、このファイルだけでなく、また私の中AVPlayerLayer Sに接続されているAVPlayerの中でプレーするAVPlayerItem秒にslowmoにマッピングされている元のファイルを読み込みますアプリ:

let firstItem = AVPlayerItem(asset: originalAsset) 
let player1 = AVPlayer(playerItem: firstItem) 
firstItem.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithmVarispeed 
player1.actionAtItemEnd = .none 
firstPlayer.player = player1 

// set up player 2 
let secondItem = AVPlayerItem(asset: renderedVideo) 
secondItem.seekingWaitsForVideoCompositionRendering = true //tried false as well 
secondItem.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithmVarispeed 
secondItem.videoComposition = nil // tried AVComposition(propertiesOf: renderedVideo) as well 

let player2 = AVPlayer(playerItem: secondItem) 
player2.actionAtItemEnd = .none 
secondPlayer.player = player2 

これらの動画を何度も繰り返し再生するための開始時刻と終了時刻があります。私は最後に興味がないので、PlayerItemDidReachEndを使用しないで、私はユーザーが入力した時間に興味があります。私もビデオを再生しようとする前に、両方のプレイヤーが求めて終了したことを確保にdispatchGroup使用:

func playAllPlayersFromStart() { 

    let dispatchGroup = DispatchGroup() 

    dispatchGroup.enter() 

    firstPlayer.player?.currentItem?.seek(to: startTime, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero, completionHandler: { _ in 
     dispatchGroup.leave() 
    }) 

    DispatchQueue.global().async { [weak self] in 
     guard let startTime = self?.startTime else { return } 
     dispatchGroup.wait() 

     dispatchGroup.enter() 

     self?.secondPlayer.player?.currentItem?.seek(to: startTime, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero, completionHandler: { _ in 
      dispatchGroup.leave() 
     }) 


     dispatchGroup.wait() 

     DispatchQueue.main.async { [weak self] in 
      self?.firstPlayer.player?.play() 
      self?.secondPlayer.player?.play() 
     } 
    } 

} 

ここで奇妙な部分があることも、私のCOMPOSE()関数のループを経由してマップされた元のアセット、完璧に良い。しかしながら、CMTimeMappingセグメントのうちの1つの間にシークすると、compose()関数によっても実行されたrenderedVideoがフリーズすることがあります。フリーズするファイルとフリーズしないファイルの唯一の違いは、AVAssetExportSessionを介してNSTemporaryDirectoryにエクスポートされ、2つのビデオトラックを1つにまとめることだけです。どちらも同じ期間です。またフリーズしているプレイヤーにBoundaryTimeObserversを追加すると、まだフリーズしていてループしてしまうので、フリーズしているのはオーディオレイヤーではないことだけです。また、オーディオも適切にループします。

私にとっては、ビデオが「フリーズ」後にシークを開始するために一時停止した場所を過ぎた場合、ビデオが「再開する」ということです。私は数日間このことに固執していて、本当にいくつかの指導を愛するでしょう。注意すべき

その他の奇妙なもの: - エクスポートされた資産に対して、元のCMTimeMappingがまったく同じ期間であっても、あなたは、レンダリングされた資産のスローモーションランプはオリジナルよりもより「途切れ」であることがわかります。 - ビデオがフリーズするとオーディオが続きます。 - スローモーションセクションの間にビデオがほとんど凍結しない(セグメントに基づくCMTimeMappingオブジェクトに起因する) - レンダリングされたビデオは、最初に "キャッチアップ"しなければならないように見える。奇妙な部分はセグメントがまったく同じで、2つの別々のソースファイルを参照することです.1つは資産ライブラリにあり、もう1つはNSTemporaryDirectoryにあります - それはそうだと私には思われます私がプレイする前にAVPlayerとAVPlayerItemStatusが 'readyToPlay'であることを確認してください。 - プレイヤーがロックアップしたポイントをPASTに進めると、 'アンフリーズ'するように見えます - 'AVPlayerItemPlaybackDidStall'のオブザーバーを追加しようとしましたと呼ばれる。

乾杯!

答えて

2

問題はAVAssetExportSessionにありました。驚いたことに、exporter.canPerformMultiplePassesOverSourceMediaData = trueを変更すると問題が解決しました。ドキュメントはかなり疎であり、このプロパティをtrueに設定しても効果がないと主張していますが、問題を修正するように見えました。非常に、非常に、非常に奇妙な!私はこれをバグとみなし、レーダーを提出する予定です。プロパティ上のドキュメントは次のとおりです。canPerformMultiplePassesOverSourceMediaData

0

playAllPlayersFromStart()メソッドでは、startTime変数がディスパッチされた2つのタスク間で変更されている可能性があります(この値はスクラブに基づいて更新される場合があります)。

startTimeのローカルコピーを機能の開始時に作成し、それを両方のブロックで使用すると、より良い運を得ることができます。

+0

本当に感謝します。残念なことに、第2のプレイヤーがまだフリーズするので、ダイスはありません。境界時間オブザーバがプレイヤー1に再始動を知らせる適切な場所に当たったので、プレイヤー自身がまだ遊んでいると私は言うことができます。また、オーディオは最初から適切に再開し、ビデオは「フリーズ」フレームを通過するまでロックアップします。 –