Post

Replies

Boosts

Views

Activity

AVMutableVideoCompositionLayerInstruction -Misalignment when Merging Videos
I followed the [Ray Wenderlich]tutorial to merge videos. The finished result is 1 merged video where portrait videos are at the top of the screen and landscape videos are at the bottom of the screen. In the image below the portrait videos plays first and then landscape video plays after it. The landscape video is from the Photos Library. code: let mixComposition = AVMutableComposition() let videoCompositionTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) let audioCompositionTrack = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) var count = 0 var insertTime = CMTime.zero var instructions = [AVMutableVideoCompositionInstruction]() for videoAsset in arrOfAssets { let audioTrack = videoAsset.tracks(withMediaType: .audio)[0] do { try videoCompositionTrack?.insertTimeRange(CMTimeRangeMake(start: .zero, duration: videoAsset.duration), of: videoAsset.tracks(withMediaType: .video)[0], at: insertTime) try audioCompositionTrack?.insertTimeRange(CMTimeRangeMake(start: .zero, duration: videoAsset.duration), of: audioTrack, at: insertTime) let layerInstruction = videoCompositionInstruction(videoCompositionTrack!, asset: videoAsset, count: count) let videoCompositionInstruction = AVMutableVideoCompositionInstruction() videoCompositionInstruction.timeRange = CMTimeRangeMake(start: insertTime, duration: videoAsset.duration) videoCompositionInstruction.layerInstructions = [layerInstruction] instructions.append(videoCompositionInstruction) insertTime = CMTimeAdd(insertTime, videoAsset.duration) count += 1 } catch { } } let videoComposition = AVMutableVideoComposition() videoComposition.instructions = instructions videoComposition.frameDuration = CMTimeMake(value: 1, timescale: 30) videoComposition.renderSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height) // ... exporter.videoComposition = videoComposition AVMutableVideoCompositionLayerInstruction: func videoCompositionInstruction(_ track: AVCompositionTrack, asset: AVAsset, count: Int) -> AVMutableVideoCompositionLayerInstruction { let instruction = AVMutableVideoCompositionLayerInstruction(assetTrack: track) let assetTrack = asset.tracks(withMediaType: .video)[0] let transform = assetTrack.preferredTransform let assetInfo = orientationFromTransform(transform) var scaleToFitRatio = UIScreen.main.bounds.width / assetTrack.naturalSize.width if assetInfo.isPortrait { scaleToFitRatio = UIScreen.main.bounds.width / assetTrack.naturalSize.height let scaleFactor = CGAffineTransform(scaleX: scaleToFitRatio, y: scaleToFitRatio) instruction.setTransform(assetTrack.preferredTransform.concatenating(scaleFactor), at: .zero) } else { let scaleFactor = CGAffineTransform(scaleX: scaleToFitRatio, y: scaleToFitRatio) var concat = assetTrack.preferredTransform.concatenating(scaleFactor) .concatenating(CGAffineTransform(translationX: 0,y: UIScreen.main.bounds.width / 2)) if assetInfo.orientation == .down { let fixUpsideDown = CGAffineTransform(rotationAngle: CGFloat(Double.pi)) let windowBounds = UIScreen.main.bounds let yFix = assetTrack.naturalSize.height + windowBounds.height let centerFix = CGAffineTransform(translationX: assetTrack.naturalSize.width, y: yFix) concat = fixUpsideDown.concatenating(centerFix).concatenating(scaleFactor) } instruction.setTransform(concat, at: .zero) } if count == 0 { instruction.setOpacity(0.0, at: asset.duration) } return instruction } Orientation: func orientationFromTransform(_ transform: CGAffineTransform) -> (orientation: UIImage.Orientation, isPortrait: Bool) { var assetOrientation = UIImage.Orientation.up var isPortrait = false let tfA = transform.a let tfB = transform.b let tfC = transform.c let tfD = transform.d if tfA == 0 && tfB == 1.0 && tfC == -1.0 && tfD == 0 { assetOrientation = .right isPortrait = true } else if tfA == 0 && tfB == -1.0 && tfC == 1.0 && tfD == 0 { assetOrientation = .left isPortrait = true } else if tfA == 1.0 && tfB == 0 && tfC == 0 && tfD == 1.0 { assetOrientation = .up } else if tfA == -1.0 && tfB == 0 && tfC == 0 && tfD == -1.0 { assetOrientation = .down } return (assetOrientation, isPortrait) }
2
0
1.4k
Feb ’23
AppReview Rejected Subscription Because in-app purchase API returned "Can't Connect to AppStore - Retry"
On my purchase page I use RevenueCat to make the initial purchase and in my SettingsVC I have the in-app purchase API (as required) to later resubscribe: // ... try await AppStore.showManageSubscriptions(in: window as! UIWindowScene) I followed these directions and these directions. Using an iPhone 8 simulator I logged into iCloud as a sandbox tester eg. sandboxtest%test.com, then logged into its Settings, loaded my app, made a purchase, the subscription went through and eventually expired. While in the iPhone 8 I checked my SettingsVC > in-app purchase API and it said Expired Nov 5, 2023 ... Select an option to resubscribe. So it worked. I send sandboxtest%test.com and the pw to App Review and got the below rejection: Guideline 2.1 - Performance - App Completeness We discovered one or more bugs in your app. Specifically, your app displayed an error page when the Mange Subscription tab was tapped. We found that while you have submitted in-app purchase products for your app, the in-app purchase functionality is not present in your binary. If you would like to include in-app purchases in your app, you will need to upload a new binary that incorporates the in-app purchase API to enable users to make a purchase They sent me a screenshot and the in-app purchase API said Cannot Connect - Retry. I later use my actual device and try these 3 ways: 1- While logged in as myself, without using any Sandbox Account, delete the app, run it again, then log into my app with sandboxtest%test.com 2- While logged in as myself, use a different sandbox tester such as whatever%test.com to log into the Sandbox Account, delete the app, run it again, then log into my app with sandboxtest%test.com 3- While logged in as myself, use sandboxtest%test.com for the Sandbox Account, delete the app, run it again, then log into my app with sandboxtest%test.com For all 3 RevenueCat prints the initial subscription and the expiration date, but for some reason when I go to the in-app purchase API, it always returns You do not have any subscriptions. What's strange is when I go back to the iPhone 8 while still logged in as sandboxtest%test.com, the in-app purchase API still shows Expired Nov 5, 2023 ... Select an option to resubscribe. I'm kinda lost here because to use an actual device to login, it sends a SMS, so I don't see how giving the App Reviewer the sandboxtest%test.com/pw info to login into the device and iCloud will help him/her make a purchase because they can't get the SMS. I would assume they would only need sandboxtest%test.com, but that does't work for them. Any advice?
0
0
847
Nov ’23