I am getting this error when I am trying to play multiple videos using this swift library (https://github.com/piemonte/player). Not sure if it's related to that player, or to the Photos framework or what though.
What happens is, I have a view that will display either a photo or a video. Everything works fine a few times until a few videos have played and then this message will pop up, followed by all the videos not being able to play and in their place you just see a black screen, and then I get a memory usage error.
I am using a library called SwipeView and here is some relevant code which may be helpful.
func swipeView(swipeView: SwipeView!, viewForItemAtIndex index: Int, reusingView view: UIView!) -> UIView! {
let asset: PHAsset = self.photosAsset[index] as PHAsset
// Create options for retrieving image (Degrades quality if using .Fast)
// let imageOptions = PHImageRequestOptions()
// imageOptions.resizeMode = PHImageRequestOptionsResizeMode.Fast
var imageView: UIImageView!
let screenSize: CGSize = UIScreen.mainScreen().bounds.size
let targetSize = CGSizeMake(screenSize.width, screenSize.height)
var options = PHImageRequestOptions()
options.resizeMode = PHImageRequestOptionsResizeMode.Exact
options.synchronous = true
if (asset.mediaType == PHAssetMediaType.Image) {
PHImageManager.defaultManager().requestImageForAsset(asset, targetSize: targetSize, contentMode: .AspectFill, options: options, resultHandler: {(result, info) in
if (result.size.width > 200) {
imageView = UIImageView(image: result)
}
})
return imageView
} else if (asset.mediaType == PHAssetMediaType.Video) {
self.currentlyPlaying = Player()
PHImageManager.defaultManager().requestAVAssetForVideo(asset, options: nil, resultHandler: {result, audio, info in
self.currentlyPlaying.delegate = self
self.currentlyPlaying.playbackLoops = true
self.addChildViewController(self.currentlyPlaying)
self.currentlyPlaying.didMoveToParentViewController(self)
var t = result as AVURLAsset
var url = t.valueForKey("URL") as NSURL
var urlString = url.absoluteString
self.currentlyPlaying.path = urlString
})
return self.currentlyPlaying.view
}
return UIView()
}
func swipeViewItemSize(swipeView: SwipeView!) -> CGSize {
return self.swipeView.bounds.size;
}
func swipeView(swipeView: SwipeView!, didSelectItemAtIndex index: Int) {
self.currentlyPlaying.playFromBeginning()
}
func swipeViewCurrentItemIndexDidChange(swipeView: SwipeView!) {
self.currentlyPlaying.stop()
}
Any thoughts would be great.
I had this same "Connection to assetsd was interrupted or assetsd died" error.
Usually followed by a memory warning.
I tried to find retain cycles, but it wasn't it.
The problem for me had to do with the known fact that the resultHandler block of any PHImageManager request...() is not called on the main queue.
So we cannot run UIView code directly within those blocks without asking for trouble later on in the app life.
To fix this we may for instance run all UIView code that would be executed within the scope of the resultHandler block inside a dispatch_async(dispatch_get_main_queue(), block)
I was having this problem because I did not realize that one of the functions that I was calling within one of the resultHandler.s of my app did eventually call some UIView code down the line.
And so I was not forwarding that call to the main thread.
Hope this helps.
The issue is occurring because of the TargetSize only. The PHImageManagerMaximumSize option is the culprit. Set the CGSIZE as you want for the imageview.
Your problem might be that you create a new Player-object everytime you start playing a video:
self.currentlyPlaying = Player()
I would recommend you to either remove it after the file has finished playing or to create one player which you will reuse. Otherwise it will stay in the memory.v
Related
I have a tableview which sets a UIImage to hold either an image url from AWS or a thumbnail generated from a video URL also from AWS. The video url refuses to display in my tableview and it throws this error in the debugger.
2017-12-29 12:20:37.053337-0800 VideoFit[3541:1366125] CredStore - performQuery - Error copying matching creds. Error=-25300, query={
class = inet;
"m_Limit" = "m_LimitAll";
"r_Attributes" = 1;
sync = syna;
}
When I click the cell to display either the image or video url, it segues to the video player correctly and then I get the error again and the triangular start button has a line through it signifying that there is no video to be played.
But when I print the url it has successfully passed so that is not the issue, the issue is AVPlayer can't handle my AWS video url for some reason. It is an https link so that means it must be secure but I already set my arbitrary loads to true
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Here is some code for displaying my videos in the videoPlayer VC and also the thumbnail generator function, perhaps there is some issue lying with these?
import UIKit
import AVKit
import MediaPlayer
class VideoPlayerVC: AVPlayerViewController {
var urlToPlay: String?
override func viewDidLoad() {
super.viewDidLoad()
print("Here is the url ---> \(String(describing: urlToPlay))")
playVideo()
}
private func playVideo() {
guard let urlFromString = urlToPlay else { print("No url to play") ;return }
let url = URL(string: urlFromString)
print("Here is the url to play ---> \(String(describing: url))")
let asset: AVURLAsset = AVURLAsset(url: url!)
let item: AVPlayerItem = AVPlayerItem(asset: asset)
let player: AVPlayer = AVPlayer(playerItem: item)
self.player = player
self.showsPlaybackControls = true
self.player?.play()
}
}
This is how I make the thumbnail, when my cellforRow atIndexPath method runs it throws this error for every video object in the tableview.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "sortedExerciseCell") as? SortedExerciseCell! {
// Do if check for videoURI and imageURI
if selectedExerciseArray[indexPath.row].imageURI != nil {
if let imageURI = URL(string: selectedExerciseArray[indexPath.row].imageURI!) {
print("It's a photo!")
// Using DispatchQueue.global(qos: .background).async loads cells in background
DispatchQueue.global(qos: .background).async {
let data = try? Data(contentsOf: imageURI)
DispatchQueue.main.async {
cell.exerciseImg.image = UIImage(data: data!)
}
}
}
} else {
if let videoURI = URL(string: selectedExerciseArray[indexPath.row].videoURI!) {
print("It's a video!")
print(videoURI)
DispatchQueue.global(qos: .background).async {
DispatchQueue.main.async {
cell.exerciseImg.image = self.thumbnailForVideoAtURL(url: videoURI)
// for every video object the above error is thrown!!!
}
}
}
}
cell.exerciseName.text = selectedExerciseArray[indexPath.row].name
return cell
} else {
return UITableViewCell()
}
}
// Used to just display the video thumbnail in cell, when clicked on will display video as needed
private func thumbnailForVideoAtURL(url: URL) -> UIImage? {
let asset = AVAsset(url: url)
let assetImageGenerator = AVAssetImageGenerator(asset: asset)
do {
print("Doing the video thumbnail from backend")
let imageRef = try assetImageGenerator.copyCGImage(at: CMTimeMake(1, 60) , actualTime: nil)
return UIImage(cgImage: imageRef)
} catch {
print("This is failing for some reason")
print("error")
return nil
}
}
I've looked at similar questions on stack overflow about this but none seem to give a complete answer on how to solve this problem, most chalking it up to an iOS 11 bug that can't be fixed or transport security (Already have arbitrary loads on so this can't be the issue..) anyone else have any approaches that might work?
Important note - My backend developer can only view the video url's from a webpage, in other words he must make a basic website to display the video after downloading it from the web. I'm not sure if this is standard procedure for handling video url's from AWS but I decided to try loading the url with "loadHTMLString" in a custom UIViewController conforming to WKUIDelegate, I see the video but the same situation happens where the triangular start button is crossed out signifying no video can be played. I'm not really sure what else I can try at this moment, any help is appreciated.
Here is one of the links I've pulled from my backend.
https://videofitapptestbucket.s3.us-west-2.amazonaws.com/100001427750
It seems that your problem is in file extension. DMS is not recognized by OS, it throws when creating image with assetgenerator.
Error Domain=AVFoundationErrorDomain Code=-11828 "Cannot Open" UserInfo={NSLocalizedFailureReason=This media format is not supported., NSLocalizedDescription=Cannot Open, NSUnderlyingError=0x6040000534d0 {Error Domain=NSOSStatusErrorDomain Code=-12847 "(null)"}}
Rename your files on server. The mp4 extension seems to work just fine with your file.
Also, subclassing AVPlayerViewController is generally not that great idea as Apple says that it will result in undefined behavior. (read: random mess). I would suggest to use class that have AVPlayer inside.
Try:
if let path = urlToPlay {
let url = URL(string: path)!
let videoPlayer = AVPlayer(url: url)
self.player = videoPlayer
DispatchQueue.main.async {
self.player?.play()
}
}
Call it in viewDidAppear() method
I have a UIImageView that I use for users profile pic. But, the image appears late on screen like 2 to 3 seconds. Here is how I download it:
#IBOutlet weak var ProfilePic: UIImageView!
override func awakeFromNib() {
FriendSystem.system.USER_REF.child(user.uid).observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.hasChild("imageUrl"){
self.firebaseStorage.child("users").child(FriendSystem.system.CURRENT_USER_ID).getData(maxSize: 10*1024*1024, completion: { (data, error) in
let userPhoto = UIImage(data: data!)
self.ProfilePic.image = userPhoto
})
}
})
}
How can I solve this?
The code you're running runs on a different Thread, and according to Apple, all UI updates must run on the Main Thread. Here's a link that explains why all UI updates must be on the Main Thread
So in your code you just have to use DispatchQueue like this
DispatchQueue.main.async {
// please don't use implicit unwrapping of variables `data!` cause it may cause crashes, use if let statements instead
if let data = data {
let userPhoto = UIImage(data: data)
self.ProfilePic.image = userPhoto
}
}
On a different note:
Please read on Swift coding standards in this link
Well i actually solved it and I will post it here if newbies like me encounter the same problem. I used Firebase and SDWebImage. Here is the link This is from Firebase's official website.
// Reference to an image file in Firebase Storage
let reference = storageRef.child("images/stars.jpg")
// UIImageView in your ViewController
let imageView: UIImageView = self.imageView
// Placeholder image
let placeholderImage = UIImage(named: "placeholder.jpg")
// Load the image using SDWebImage
imageView.sd_setImage(with: reference, placeholderImage: placeholderImage)
I'm using [PHCachingImageManager] to cache videos in a collection view. I can display the images generated with the cache manager, but pulling AVAssets isn't working.
I try to get an AVAsset by using the .requestAVAsset(forVideo:...) method but the [asset] that is returned to the result handler is nil. I get an error when trying to unwrap the returned [asset].
I also looked at the [audioMix] and [info] parameters returned to the result handler and they were nil was well. Does anyone know what I'm doing wrong/missing here?
viewDidLoad() {
let videosAlbum = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .smartAlbumVideos, options: nil)
self.videoAssets = PHAsset.fetchAssets(in: videosAlbum.object(at: 0), options: nil)
self.imgCacheManager.startCachingImages(for: self.videoAssets.objects(at: [0, videoAssets.count-1]), targetSize: CGSize(width: 150, height: 150), contentMode: .aspectFit, options: nil)
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let selectedAsset = self.imgCacheManager.requestAVAsset(forVideo: self.videoAssets.object(at: indexPath.item), options: nil, resultHandler: {
asset,_,_ in
self.delegate.selectedVideo(Asset: asset!) // this is the line I get the error
})
}
My delegate is an optional but for sure has something in it, so I'm confident it's the unwrapped [asset] that's throwing the error.
It looks like I was trying to request video assets that were stored in iCloud without setting the video request options to allow network access.
When requesting the video from the PHCachingImageManager, I failed to pass any PHVideoRequestOptions object. Adding the lines below just prior to requesting the asset fixes this.
let options = PHVideoRequestOptions()
options.isNetworkAccessAllowed = true
I found this thread on an open source image picker using the Photos framework. The videos I was testing with were indeed in iCloud which caused my returned asset to be nil. The linked thread also has a good example of hooking up a progress handler in objective C:
options.networkAccessAllowed = YES;
options.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info)
{
...
};
I am using a custom image/video picker to pick a time lapse, once the picker view has been dismissed I want to play the time lapse within a UIView that I have put on to the view controller in interface builder. This code I have currently is below but the problem is that at the moment when a time lapse is selected it is not played.
View outlet and var's for video player
#IBOutlet var videoView: UIView!
let ipc = QBImagePickerController()
var playerItem: AVPlayerItem?
var videoPlayer: AVPlayer?
var playerLayer: AVPlayerLayer?
did finish picking assets code
func qb_imagePickerController(imagePickerController: QBImagePickerController, didFinishPickingAssets assets: [AnyObject]) {
for asset in assets as! [PHAsset] {
PHImageManager.defaultManager().requestAVAssetForVideo(asset, options: nil, resultHandler: {(avAsset: AVAsset?, audioMix: AVAudioMix?, info: [NSObject : AnyObject]?) -> Void in
self.playerItem = AVPlayerItem(asset: avAsset!)
self.videoPlayer = AVPlayer(playerItem: self.playerItem!)
self.playerLayer = AVPlayerLayer(player: self.videoPlayer)
self.playerLayer!.frame = self.videoView.bounds
})
}
self.dismissViewControllerAnimated(true, completion: { self.play() })
}
play() function code
func play(){
videoPlayer!.play()
}
unexpectedly found nil when unwrapping an optional value
I don't really know about the library you are using, but it appears to have completion blocks. These are ran in the background thread.
Actually there is a problem : you dismiss your view controller before the variables (player, item etc) were assigned.
Assuming you're new to Swift I'm gonna detail you how your code will behave at runtime :
• Function is called by the delegate when your user picks a photo
• View controller is dismissed, and Player starts playing
• And then your for-loop is ran and the player is assigned an item to play (too late)
What you want to achieve is assigning an item to play and then dismiss the VC. As UI code can only be ran in the foreground (Main Thread), you cannot just move the dismiss function in the completion block, but you will need to use Grand Central Dispatch to run code in the main thread once for-loop finished.
I advise you to download the Async library in GitHub, which is a wrapper around GCD (once downloaded browse to the Sources folder and drag the Async.swift file in your Xcode project).
Then you will want to edit the "did finish picking assets" function to this in order to play your video :
func qb_imagePickerController(imagePickerController: QBImagePickerController, didFinishPickingAssets assets: [AnyObject]) {
let photoAssets = assets as! [PHAsset]
PHImageManager.defaultManager().requestAVAssetForVideo(photoAssets[0], options: nil, resultHandler: {(avAsset: AVAsset?, audioMix: AVAudioMix?, info: [NSObject : AnyObject]?) -> Void in
self.playerItem = AVPlayerItem(asset: avAsset!)
self.videoPlayer = AVPlayer(playerItem: self.playerItem!)
self.playerLayer = AVPlayerLayer(player: self.videoPlayer)
self.playerLayer!.frame = self.videoView.bounds
Async.main { self.dismissViewControllerAnimated(true, completion: { self.play() }) }
})
}
BTW I removed the for-loop coz I figured out you were just willing to pick the first asset in the array. (if wrong tell me!)
Go and try this out and tell me if it works
(More on GCD here!)
I have a camera preview which works great. I want to take a screenshot of it and everything on top of it. However, since the usual screenshot approach: CALayer's renderInContext does not render content from the camera, I need to add it separately.
I have this function in my view controller captures the image and saves it to the camera roll.
#IBAction func snapStillImage(sender: AnyObject) {
print("snapStillImage")
(self.previewView.layer as! AVCaptureVideoPreviewLayer).connection.enabled=false;
dispatch_async(self.sessionQueue, {
// Update the orientation on the still image output video connection before capturing.
let videoOrientation = (self.previewView.layer as! AVCaptureVideoPreviewLayer).connection.videoOrientation
self.stillImageOutput!.connectionWithMediaType(AVMediaTypeVideo).videoOrientation = videoOrientation
// Flash set to Auto for Still Capture
ViewController.setFlashMode(AVCaptureFlashMode.Auto, device: self.videoDeviceInput!.device)
self.stillImageOutput!.captureStillImageAsynchronouslyFromConnection(self.stillImageOutput!.connectionWithMediaType(AVMediaTypeVideo), completionHandler: {
(imageDataSampleBuffer: CMSampleBuffer!, error: NSError!) in
if error == nil {
let data:NSData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataSampleBuffer)
let image:UIImage = UIImage( data: data)!
let libaray:ALAssetsLibrary = ALAssetsLibrary()
let orientation: ALAssetOrientation = ALAssetOrientation(rawValue: image.imageOrientation.rawValue)!
libaray.writeImageToSavedPhotosAlbum(image.CGImage, orientation: orientation, completionBlock: nil)
print("save to album")
}else{
// print("Did not capture still image")
print(error)
}
})
})
}
I want to return the image defined with let image:UIImage = UIImage( data: data)!
so that it is accessible by the following function
#IBAction func downloadButton(sender: UIButton) {
//Create the UIImage
UIGraphicsBeginImageContextWithOptions(previewView.frame.size, false, 0.0)
previewView.layer.renderInContext(UIGraphicsGetCurrentContext()!)
//ALSO ADD image captured with captureStillImageAsynchronouslyFromConnection right here to the context.
let image = UIGraphicsGetImageFromCurrentImageContext()
//Save it to the camera roll
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
This would let my screenshot include the recently captured image as well as all the stuff on top that gets added with UIGraphicsGetCurrentContext()!
I have heard that you do this by including the image in the completion handler block already included in the captureStillImageAsynchronouslyFromConnection function but I am not quite sure how to do this. Any suggestions?
You could add the captured still image as a UIImage as a subview of the previewView. Then make the call to renderInContext.
Assuming this code is all going on in the same class, you can make image a property like
class CameraViewController: UIViewController {
var image: UIImage!
...
Then in captureStillImageAsynchronouslyFromConnection
instead of
let image:UIImage = UIImage( data: data)!
use
self.image = UIImage( data: data)!
Then in downloadButton use
var imageView = UIImageView(frame: previewView.frame)
imageView.image = self.image
previewView.addSubview(imageView)
previewView.layer.renderInContext(UIGraphicsGetCurrentContext()!)
You can then remove the imageView if you want
imageView.removeFromSuperview()
You cannot render content from the camera because CALayer's renderInContext doesn't work.
Nonetheless you want to work that, the following link can help you.
Taking a screenshot when camera is on -iOS
https://developer.apple.com/library/ios/qa/qa1702/_index.html#//apple_ref/doc/uid/DTS40010192