ios – Video playback orientation iOS17


Photos and the many other apps with video show playback correctly whatever the recording camera orientation. Is there a simple way to accomplish the correct rotation for playback?

After a lot of digging in stackOverflow and Apple docs I finally got the orientation of video playback to work – but it feels too difficult for something that is done all the time.

Here is my solution – is there a better way?

My app is locked to landscape full screen orientation only. Following the scheme for video to metal rendering with filters as outlined in the WWDC22 “Display HDR video in EDR with AVFoundation and Metal”, each frame of video should be converted into a CIImage for input into CIFilters.

There are two steps:

  1. Determine the orientation of the recording camera
  2. Apply a corrective rotation for the playback

StackOverFlow has multiple answers for orientation of an AVAsset – the best answer seems to be from How to detect if a video file was recorded in portrait orientation, or landscape in iOS where dhallman provides a function to answer both UIInterfaceOrientation and AVCaptureDevicePosition. Extract AVAsset Orientation and Camera Position

The need for an app function to determine UIInterfaceOrientation and AVCaptureDevicePosition seems wrong – Is this hidden in the metadata somewhere? One of the other issues that hobbled me is the iOS16 change to make loading of the videoTracks and the preferredTransform an async call. So in this extension of AVAsset a function

func videoOrientation() async -> PGLDevicePosition {
    var orientation: UIInterfaceOrientation = .unknown
    var device: AVCaptureDevice.Position = .unspecified
    var myVideoTracks:[AVAssetTrack]?
    var t: CGAffineTransform = CGAffineTransformIdentity

    do {
         myVideoTracks =  try await loadTracks(withMediaType: .video)
    }
    catch {
        /// return init values of .unknown and .unspecificed
            return PGLDevicePosition(orientation: orientation, device: device)
        }
    if let videoTrack = myVideoTracks?.first {
        do {
             t = try await videoTrack.load(.preferredTransform)
        }
        catch {
            /// return init values of .unknown and .unspecificed
            return PGLDevicePosition(orientation: orientation, device: device)
        }

The async function needs to wrapped into a task such as this

    func getVideoPreferredTransform(callBack: @escaping (PGLDevicePosition) -> Void ) {

    Task {
        let devicePosition = await avPlayerItem.asset.videoOrientation()
        callBack(devicePosition)
    }
}

Now knowing the device .front or .back and orientation a switch statement is determine the correct CGImagePropertyOrientation for the AffineTransform for the CIImage. The switch statement is

var result = CGImagePropertyOrientation.up
        // default
    switch (imageOrientation.orientation, imageOrientation.device) {
        case (.unknown,.unspecified) :
            result = CGImagePropertyOrientation.up
            
        case (.portrait, .front) :
            result = CGImagePropertyOrientation.right
        case (.portraitUpsideDown, .front):
            result = CGImagePropertyOrientation.right
        case (.landscapeLeft, .front) :
            result = CGImagePropertyOrientation.up
        case (.landscapeRight, .front) :
            result = CGImagePropertyOrientation.up

        case (.portrait, .back) :
            result = CGImagePropertyOrientation.right
        case (.portraitUpsideDown, .back):
            result = CGImagePropertyOrientation.left
        case (.landscapeLeft, .back) :
            result = CGImagePropertyOrientation.down
        case (.landscapeRight, .back) :
            result = CGImagePropertyOrientation.up

        default:
            return result // default .up
    }
    return result

Finally, the correction to the ciImage can be made. First the CIImage is converted from the cvPixelBuffer as suggested in the WWDC22 workshop. Then transform with the correct CGImagePropertyOrientation is applied to the CIImage.

  let sourceFrame = CIImage(cvPixelBuffer: buffer)

  let neededTransform = sourceFrame.orientationTransform(for: videoPropertyOrientation)
   videoCIFrame = sourceFrame.transformed(by: neededTransform)

And we are done… Seems way too complicated.. Isn’t there a simpler way???

Latest articles

spot_imgspot_img

Related articles

Leave a reply

Please enter your comment!
Please enter your name here

spot_imgspot_img