I am trying to play a video downloaded from cloudkit. I use the same query method that I use for downloading image:
publicData.performQuery(query, inZoneWithID: nil) { results, error in
if error == nil { // There is no error
for cafe in results! {
let newCafe = Cafe()
newCafe.address = cafe["address"] as? String
newCafe.name = cafe["name"] as? String
newCafe.email = cafe["email"] as? String
newCafe.description = cafe["description"] as? String
newCafe.location = cafe["location"] as? CLLocation
newCafe.cafeImage = cafe["cafeImage"] as? CKAsset
newCafe.offer_wifi = cafe["offer_wifi"] as? Bool
newCafe.smoking_area = cafe["smoking_area"] as? Bool
newCafe.cafeVideo = cafe["video"] as? CKAsset // <== I want to use this
self.cafes.append(newCafe)
let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
defaults.setInteger(self.cafes.count, forKey: "PreviousCafeCount")
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
Spinner.sharedLoader.hideLoading()
})
inside the cafeDetailViewController, I create a button that trigger playing a video using AVPlayer. AVKit and AVFoundation are already imported.
#IBAction func playVideo(sender: AnyObject) {
if let file = cafeDetail.cafeVideo {
let player = AVPlayer(URL: file.fileURL)
let playerController = AVPlayerViewController()
playerController.player = player
self.addChildViewController(playerController)
self.view.addSubview(playerController.view)
playerController.view.frame = self.view.frame
player.play()
}
}
However the result is this:
follow up question: how can I implement model association in swift? Similar to has_many and belongs_to association in rails. I don't think downloading the whole video beforehand is a good solution.
From what I can see, you need to save the video to a local file and then play that file. This is modified from something I wrote to mess around with CloudKit.
import UIKit
import CloudKit
import AVKit
import AVFoundation
class CloudKitTestViewController: UIViewController {
let publicDatabase = CKContainer.defaultContainer().publicCloudDatabase
var videoURL: NSURL!
#IBAction func load(sender: AnyObject) {
let predicate = NSPredicate(format: "videoName = %#", "nameOfVideoGoesHere")
activityIndicator.startAnimating()
let query = CKQuery(recordType: "Videos", predicate: predicate)
publicDatabase.performQuery(query, inZoneWithID: nil) { (results, error) in
if error != nil {
dispatch_async(dispatch_get_main_queue()) {
self.notifyUser("Cloud Access Error",
message: error!.localizedDescription)
}
} else {
if results!.count > 0 {
let record = results![0]
dispatch_async(dispatch_get_main_queue()) {
!)
let video = record.objectForKey("videoVideo") as! CKAsset
self.videoURL = video.fileURL
let videoData = NSData(contentsOfURL: self.videoURL!)
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
let destinationPath = NSURL(fileURLWithPath: documentsPath).URLByAppendingPathComponent("filename.mov", isDirectory: false)
NSFileManager.defaultManager().createFileAtPath(destinationPath.path!, contents:videoData, attributes:nil)
self.videoURL = destinationPath
print(self.videoURL)
}
} else {
dispatch_async(dispatch_get_main_queue()) {
self.notifyUser("No Match Found",
message: "No record matching the address was found")
}
}
}
dispatch_async(dispatch_get_main_queue(), {
self.activityIndicator.stopAnimating()
})
}
}
override func prepareForSegue(segue: UIStoryboardSegue,
sender: AnyObject?) {
let destination = segue.destinationViewController as!
AVPlayerViewController
let url = videoURL
print(videoURL)
destination.player = AVPlayer(URL: url!)
}
}
Related
I have 3 entities ( User, Album and Photo) with the following attributes.
I cant read data from Photo ( Album 1:<->>many Photo).
This is how i save my data
var managedContext: NSManagedObjectContext!
var currentUser: User!
#IBAction func saveButtonTapped(_ sender: Any) {
let album = Album(context: managedContext)
album.albumMainPhoto = photoArray[0].jpegData(compressionQuality: 1.0) as! NSData
album.albumDescription = photoDescriptionArray[0]
album.numberOfPhotos = Int16(photoArray.count)
let photos = Photo(context: managedContext)
photos.colorAsHex = "hexTest"
photos.photo = photoArray[0].jpegData(compressionQuality: 1.0) as! NSData
photos.photoDescription = "descTest"
photos.photoStyle = "styleTest"
album.addToAlbumToPhotos(photos)
currentUser?.addToUserToAlbum(album)
do {
try managedContext.save()
} catch let error as NSError {
print("save error: \(error), description: \(error.userInfo)")
}
dismiss(animated: true, completion: nil)
}
and i retrieve data by this
guard let album = currentUser?.userToAlbum?[indexPath.row] as? Album,
let albumTitle = album.albumDescription as String?,
let albumPhotosCount = album.numberOfPhotos as Int16?,
let albumMainPhoto = album.albumMainPhoto as NSData? else {
return
}
guard let photos = Albums?.albumToPhotos?[indexPath.row] as? Photo,
let photodesc = photos.photoDescription as String?,
let photoImage = photos.photo as NSData?
else {
return
}
The first option ( guard let album) works perfectly , i cant read albumDescription, numberOfPhotos and see image albumMainPhoto. When i try to use this on photos it can't read data. I am still new to coreData, so i might do something wrong.
I was able to get data with this :
guard let album = currentUser?.userToAlbum?[indexPath.row] as? Album,
let albumTitle = album.albumDescription as String?,
let albumPhotosCount = album.numberOfPhotos as Int16?,
let albumMainPhoto = album.albumMainPhoto as NSData?,
let photos = album.albumToPhotos?[indexPath.row] as? Photo,
let photosDes = photos.photoDescription as String?
else {
return
}
I've been trying to upload audio recording right after user stops recording to the Firebase. But it doesn't do anything apart from creating a new folder named "audio".
Code I'm using for starting and stopping recording
#IBAction func recordAudio(_ sender: AnyObject) {
recordingLabel.text = "Recording in progress"
stopRecordingButton.isEnabled = true
recordButton.isEnabled = false
let dirPath = NSSearchPathForDirectoriesInDomains(.documentDirectory,.userDomainMask, true)[0] as String
let recordingName = "recordedVoice.wav"
let pathArray = [dirPath, recordingName]
let filePath = URL(string: pathArray.joined(separator: "/"))
let session = AVAudioSession.sharedInstance()
try! session.setCategory(AVAudioSessionCategoryPlayAndRecord, with:AVAudioSessionCategoryOptions.defaultToSpeaker)
try! audioRecorder = AVAudioRecorder(url: filePath!, settings: [:])
audioRecorder.delegate = self
audioRecorder.isMeteringEnabled = true
audioRecorder.prepareToRecord()
audioRecorder.record()
}
#IBAction func stopRecording(_ sender: AnyObject) {
print("Stop recording button was pressed")
recordButton.isEnabled = true
stopRecordingButton.isEnabled = false
recordingLabel.text = "Tap to Record"
audioRecorder.stop()
let audioSession = AVAudioSession.sharedInstance()
try! audioSession.setActive(false)
}
code I'm using for uploading to Firebase
func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
print("finished recording")
let storageRef = Storage.storage().reference().child("audio/recordedVoice.wav")
if let uploadData = AVFileType(self.recordedVoice.wav!) {
storageRef.put(uploadData, metadata: nil) {(metadata, error) in
if error != nil {
print(error)
return
}
}
}
}
Please help me!
Try:
let audioName = NSUUID().uuidString //You'll get unique audioFile name
let storageRef = Storage.storage().reference().child("audio").child(audioName)
let metadata = StorageMetadata()
metadata.contentType = "audio/wav"
if let uploadData = AVFileType(self.recordedVoice.wav!) {
storageRef.putData(uploadData, metadata: metadata) { (metadata, err) in
if err != nil {
//print(err)
return
}
if let _ = metadata?.downloadURL()?.absoluteString {
print("uploading done!")
}
}
}
so I have been working on this for hours and here is my answer:
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
func getFileURL() -> URL {
let path = getDocumentsDirectory()
let filePath = path.appendingPathComponent(K.fileName)
return filePath
}
let referance = storage.reference()
let mediaFolder = referance.child("media")
let id = UUID().uuidString // using uuid to give uniq names to audiofiles preventing overwrite
let mediaRef = mediaFolder.child(id + K.fileName) // creating file referance using uuid + filename
let path = getFileURL() // getting filepath
do {
let data = try Data(contentsOf: path) // getting data from filepath
mediaRef.putData(data) { metadata, error in
if error != nil {
self.showAlert(title: "Error", message: error?.localizedDescription, cancelButtonTitle: "cancel", handler: nil)
} else {
mediaRef.downloadURL { url, error in
let url = url?.absoluteString
print(url)
}
}
}
print("record has come")
} catch {
print("error cant get audio file")
}
I'm relatively new at coding and still learning. So that my answer may not be the best and shortest. But This worked for me.
func trimVideo (sourceURL: URL, destinationURL: URL, trimPoints: TrimPoints, completion: #escaping () -> Void) {
guard sourceURL.isFileURL else { return }
guard destinationURL.isFileURL else { return }
let options = [
AVURLAssetPreferPreciseDurationAndTimingKey: true
]
let asset = AVURLAsset(url: sourceURL, options: options)
let preferredPreset = AVAssetExportPresetPassthrough
if verifyPresetForAsset(preset: preferredPreset, asset: asset) {
let composition = AVMutableComposition()
let videoCompTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: CMPersistentTrackID())
let audioCompTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: CMPersistentTrackID())
guard let assetVideoTrack: AVAssetTrack = asset.tracks(withMediaType: .video).first else { return }
guard let assetAudioTrack: AVAssetTrack = asset.tracks(withMediaType: .audio).first else { return }
var accumulatedTime = kCMTimeZero
for (startTimeForCurrentSlice, endTimeForCurrentSlice) in trimPoints {
let durationOfCurrentSlice = CMTimeSubtract(endTimeForCurrentSlice, startTimeForCurrentSlice)
let timeRangeForCurrentSlice = CMTimeRangeMake(startTimeForCurrentSlice, durationOfCurrentSlice)
do {
try videoCompTrack!.insertTimeRange(timeRangeForCurrentSlice, of: assetVideoTrack, at: accumulatedTime)
try audioCompTrack!.insertTimeRange(timeRangeForCurrentSlice, of: assetAudioTrack, at: accumulatedTime)
accumulatedTime = CMTimeAdd(accumulatedTime, durationOfCurrentSlice)
}
catch let compError {
print("TrimVideo: error during composition: \(compError)")
}
}
guard let exportSession = AVAssetExportSession(asset: composition, presetName: preferredPreset) else { return }
exportSession.outputURL = destinationURL as URL
exportSession.outputFileType = AVFileType.m4v
exportSession.shouldOptimizeForNetworkUse = true
removeFileAtURLIfExists(url: destinationURL as URL)
exportSession.exportAsynchronously {
completion()
}
}
else {
print("TrimVideo - Could not find a suitable export preset for the input video")
}
}
#IBAction func nextButtonPressed(_ sender: Any) {
if MyVariables.isScreenshot == true {
//get image from current time
print("screenshot")
guard let currentTime = trimmerView.currentPlayerTime else {
return
}
self.thumbnailImage = imageFromVideo(url: footageURL!, time: currentTime )
self.screenshotOut = imageFromVideo(url: footageURL!, time: currentTime )
self.performSegue(withIdentifier: "CreatePost_Segue", sender: nil)
} else {
print("video")
let outputFileName = NSUUID().uuidString
let outputFilePath = (NSTemporaryDirectory() as NSString).appendingPathComponent((outputFileName as NSString).appendingPathExtension("mov")!)
self.videoURL = URL(fileURLWithPath: outputFilePath)
trimVideo(sourceURL: self.footageURL!, destinationURL: self.videoURL!, trimPoints: [(trimmerView.startTime!,trimmerView.endTime!)], completion: self.finishVideo)
}
}
func finishVideo() -> Void {
guard let VideoURL = self.videoURL else { return }
self.thumbnailImage = setThumbnailFrom(path: VideoURL)
print("got to segue") //This does print successfully so something is happening in the segue...
self.performSegue(withIdentifier: "CreatePost_Segue", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if segue.identifier == "CreatePost_Segue" {
let controller = segue.destination as! CreatePostViewController
controller.thumbnailImage = self.thumbnailImage
controller.videoURL = self.videoURL
controller.screenshotOut = self.screenshotOut
}
}
So in nextButtonPressed you can see that I if the. media is a video (it is), I am using the trimVideo function and a custom completion handler of finishVideo to create a thumbnail with the trimmed video as well as perform the segue itself.
Everything executes without error until the segue so I believe I am sending the data wrong perhaps? Perhaps something to do with screenshotOut not being set if its a video?
The full error is
*** Assertion failure in void _UIPerformResizeOfTextViewForTextContainer(NSLayoutManager *, UIView<NSTextContainerView> *, NSTextContainer *, NSUInteger)(), /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIFoundation/UIFoundation-546.2/UIFoundation/TextSystem/NSLayoutManager_Private.m:1619
I fixed this with the advice #aBilal17 gave to run the segue on the main thread like so:
func finishVideo() -> Void {
guard let VideoURL = self.videoURL else { return }
self.thumbnailImage = setThumbnailFrom(path: VideoURL)
print("got to segue")
DispatchQueue.main.async {
self.performSegue(withIdentifier: "CreatePost_Segue", sender: nil)
}
}
I make a simple media player. I have a UITableViewController with mp3 files from Document Directory and UIViewController which plays mp3 files. I pass NSURL of a mp3 file from UITableViewController to UIViewController and I can play it. I want to make buttons are which will be turn track to next or previous. How can I make it?
The code is passing NSURL on a specific file.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var playerVC = (segue.destinationViewController as! UINavigationController).topViewController as! PlayMusicViewController
var indexPath = tableView.indexPathForSelectedRow()
var nameOfObjectForPass = listOfMP3Files![indexPath!.row] // default it's name and
var fileManager = NSFileManager.defaultManager()
var wayToFile = fileManager.URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask)
var passMusicFileURL: NSURL? // for pass mp3
if let documentPath: NSURL = wayToFile.first as? NSURL {
let musicFile = documentPath.URLByAppendingPathComponent(nameOfObjectForPass)
println(musicFile)
passMusicFileURL = musicFile
}
if segue.identifier == "listenMusic" {
playerVC.nameMusicFile = nameOfObjectForPass // name
playerVC.mp3URL = passMusicFileURL
// test
playerVC.allUrlsVC = allURLS
}
The code is playing mp3 file
func playMusic() {
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil) // == true
AVAudioSession.sharedInstance().setActive(true, error: nil)
var error: NSError?
audioPlayer = AVAudioPlayer(contentsOfURL: mp3URL, error: &error)
// audioPlayer.prepareToPlay()
if currentPause == nil {
} else {
audioPlayer.currentTime = currentPause
}
//
audioPlayer.volume = 0.5
audioPlayer.play()
}
UPDATE
I made following
var arrayMP3url: Array<AnyObject>!
func playNextSound() {
var queue = AVQueuePlayer(URL: mp3URL)
var current = queue.currentItem
var arrayForSearch = arrayMP3url as! [NSURL]
var arrNS = arrayForSearch as NSArray
var index = arrNS.indexOfObject(mp3URL!)
println("index \(index)")
println("array for search \(arrayForSearch)")
println("current song \(current)")
}
But I always get the same index from NSArray
Use your array of mp3 files, and determine current playing index using indexPath, increase or, decrease the current playing index on button click as Next, or, Prev, and change the mp3 file by the index.
Replay the music.
Simple.
Ping if you need more help.
Update
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var playerVC = (segue.destinationViewController as! UINavigationController).topViewController as! PlayMusicViewController
var indexPath = tableView.indexPathForSelectedRow();
//We only need to pass current index, and array of mp3 urls
playerVC.curIndex=indexPath.row;
playerVC.mp3s= listOfMP3Files;
}
So as described above, just declare two properties in your playerVC, ie
curIndex as int and mp3s as NSArray.
In the function
func playMusic() {
var nameOfObjectForPass = mp3s![curIndex]; // default it's name and
var fileManager = NSFileManager.defaultManager()
var wayToFile = fileManager.URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask)
var passMusicFileURL: NSURL? // for pass mp3
if let documentPath: NSURL = wayToFile.first as? NSURL {
let musicFile = documentPath.URLByAppendingPathComponent(nameOfObjectForPass)
println(musicFile)
passMusicFileURL = musicFile
}
self.nameMusicFile = nameOfObjectForPass // name
self.mp3URL = passMusicFileURL
// test
self.allUrlsVC = allURLS
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil) // == true
AVAudioSession.sharedInstance().setActive(true, error: nil)
var error: NSError?
audioPlayer = AVAudioPlayer(contentsOfURL: mp3URL, error: &error)
// audioPlayer.prepareToPlay()
if currentPause == nil {
} else {
audioPlayer.currentTime = currentPause
}
//
audioPlayer.volume = 0.5
audioPlayer.play()
}
Then simply define your next and previous index calculations like
func playNextSound(){
self.curIndex++;
var maxCount=self.mp3s.count-1;
if(self.curIndex>maxCount){
self.curIndex=maxCount;
}
playMusic();
}
func playPrevSound(){
self.curIndex--;
if(self.curIndex<0){
self.curIndex=0;
}
playMusic();
}
Hope it helps. Cheers.
How do I upload and load back images from cloud kit with swift?
What attribute type do I use?
What code do I use? This is the code I use currently...
func SaveImageInCloud(ImageToSave: UIImage) {
let newRecord:CKRecord = CKRecord(recordType: "ImageRecord")
newRecord.setValue(ImageToSave, forKey: "Image")
if let database = self.privateDatabase {
database.saveRecord(newRecord, completionHandler: { (record:CKRecord!, error:NSError! ) in
if error != nil {
NSLog(error.localizedDescription)
}
else {
dispatch_async(dispatch_get_main_queue()) {
println("finished")
}
}
})
}
You need to create a CKAsset and add that to your record. You can do that with code like this:
func SaveImageInCloud(ImageToSave: UIImage) {
let newRecord:CKRecord = CKRecord(recordType: "ImageRecord")
let nsDocumentDirectory = NSSearchPathDirectory.DocumentDirectory
let nsUserDomainMask = NSSearchPathDomainMask.UserDomainMask
if let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true) {
if paths.count > 0 {
if let dirPath = paths[0] as? String {
let writePath = dirPath.stringByAppendingPathComponent("Image2.png")
UIImagePNGRepresentation(ImageToSave).writeToFile(writePath, atomically: true)
var File : CKAsset? = CKAsset(fileURL: NSURL(fileURLWithPath: writePath))
newRecord.setValue(File, forKey: "Image")
}
}
}
if let database = self.privateDatabase {
database.saveRecord(newRecord, completionHandler: { (record:CKRecord!, error:NSError! ) in
if error != nil {
NSLog(error.localizedDescription)
} else {
dispatch_async(dispatch_get_main_queue()) {
println("finished")
}
}
})
}
Here's something similar to Edwin's answer but a little more compact. I've tested this and it works well.
This example is saving "myImage" UIImageView into "mySaveRecord" CKRecord, just replace those names with your respective ones.
let documentDirectory = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! String
let imageFilePath = documentDirectory.stringByAppendingPathComponent("lastimage")
UIImagePNGRepresentation(myImage).writeToFile(imageFilePath, atomically: true)
let asset = CKAsset(fileURL: NSURL(fileURLWithPath: imageFilePath))
mySaveRecord.setObject(asset, forKey: "ProfilePicture")
CKContainer.defaultContainer().publicCloudDatabase.saveRecord(mySaveRecord, completionHandler: {
record, error in
if error != nil {
println("\(error)")
} else {
//record saved successfully!
}
})
I created this little extension in Swift 5 to convert from UIImage to CKAsset:
extension UIImage {
func toCKAsset(name: String? = nil) -> CKAsset? {
guard let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first else {
return nil
}
guard let imageFilePath = NSURL(fileURLWithPath: documentDirectory)
.appendingPathComponent(name ?? "asset#\(UUID.init().uuidString)")
else {
return nil
}
do {
try self.pngData()?.write(to: imageFilePath)
return CKAsset(fileURL: imageFilePath)
} catch {
print("Error converting UIImage to CKAsset!")
}
return nil
}
}
You can then use it as you have it in your question:
if let asset = ImageToSave.toCKAsset() {
newRecord.setObject(asset, forKey: "Image")
CKContainer.defaultContainer().publicCloudDatabase.saveRecord(newRecord, completionHandler: {
record, error in
if error != nil {
println("\(error)")
} else {
// saved successfully!
}
})
}
This answer works with Swift 2.2 & iOS 9, and separates the file creation from the upload so that you can properly test against both, since they are distinct actions with their own potential issues.
For the uploadPhoto function, the recordType variable is the value you use in your CloudKit dashboard. The "photo" key in the photo["photo"] = asset line is the field name for your record type.
func uploadPhoto(image: UIImage, recordName: String) {
let privateDB = CKContainer.defaultContainer().privateCloudDatabase
let photoID = CKRecordID(recordName: recordName)
let photo = CKRecord(recordType: recordType, recordID: photoID)
let asset = CKAsset(fileURL: writeImage(image))
photo["photo"] = asset
privateDB.saveRecord(photo) { (record, error) in
guard error == nil else {
print(error?.localizedDescription)
return
}
print("Successful")
}
}
func writeImage(image: UIImage) -> NSURL {
let documentsURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
let fileURL = documentsURL.URLByAppendingPathComponent(NSUUID().UUIDString + ".png")
if let imageData = UIImagePNGRepresentation(image) {
imageData.writeToURL(fileURL, atomically: false)
}
return fileURL
}
You can call this with the following:
uploadPhoto(UIImage(named: "foo.png")!, recordName: "bar")
You'll want to pick the Asset value type in the dashboard for this value.
newRecord.setValue(ImageToSave, forKey: "Image")
UIImage is not an allowed type on CKRecord. Your best option is to write this image out to a file, then create a CKAsset and set that on the record.