It seems that I can't stream my videos once I downloaded them from Parse backend. However I know that the videos are working well as you can play it from Chrome but not from Safari ( I don't know why ...).
My code to play the video on the AVPlayer is also right since I test it for multiple NSUrl from local database.
Several posts about it confirm that there is a problem going on but no one has an answer.
iOS - Can't stream video from Parse Backend
Cant stream video (PFFile) from parse server
Thanks in advance if someone can help me.
The secret is to get the PFFIle data, save it locally with the proper extention (mov) and treaming the local file.
look at this entry [Cant stream video (PFFile) from parse server][1], the second anwser.
The PFFIle was created with the following code:
let videoPath = info[UIImagePickerControllerMediaURL] as! NSURL
let imageData = NSData (contentsOfURL:videoPath)! as NSData
do {
let file = try PFFile(name: "_mov", data: imageData, contentType: "video/quicktime")
videoSaveSelected(file )
} catch let error as NSError {
print("Error generating PFFile: \(error)")
}
This is the code for the player
import UIKit
import AVKit
import MobileCoreServices
import AssetsLibrary
import AVFoundation
import Parse
import ParseUI
class VideoPlayerViewController: UIViewController {
var videoUrl: NSURL!
var file: PFFile!
override func viewDidLoad() {
super.viewDidLoad()
// navigation
let backButtonCustom = UIButton( frame: CGRectMake(0, 0, 22, 22))
let backIcon = UIImage(named: "0836-arrow-left")?.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)
backButtonCustom.setImage(backIcon, forState: UIControlState.Normal)
backButtonCustom.addTarget(self, action: #selector(ReqDataEntryLgViewController.doneButtonTapped), forControlEvents: UIControlEvents.TouchUpInside)
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: backButtonCustom)
let doneButton = UIBarButtonItem(title: "Done", style: .Done, target: self, action: #selector(ReqDataEntryLgViewController.doneButtonTapped))
self.navigationItem.setRightBarButtonItems([doneButton], animated: true)
// video player
let playerController = AVPlayerViewController()
self.addChildViewController(playerController)
self.view.addSubview(playerController.view)
playerController.view.frame = self.view.frame
// get data from PFFile in database
file!.getDataInBackgroundWithBlock({
(movieData: NSData?, error: NSError?) -> Void in
if (error == nil) {
let documentsPath : AnyObject = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,.UserDomainMask,true)[0]
let destinationPath:NSString = documentsPath.stringByAppendingString("/file.mov")
let filemanager = NSFileManager.defaultManager()
do {
try filemanager.removeItemAtPath(destinationPath as String)
} catch {
print("Ooops! Something went wrong: \(error)")
}
// write to local file
movieData!.writeToFile ( destinationPath as String, atomically:true)
// do {
// let attr : NSDictionary? = try NSFileManager.defaultManager().attributesOfItemAtPath(destinationPath as String)
//
// if let _attr = attr {
// let fileSize = _attr.fileSize()
// print ("Movie Size \(fileSize)" )
// }
// } catch {
// print("Ooops! Something went wrong getting size: \(error)")
// }
let playerItem = AVPlayerItem(asset: AVAsset(URL: NSURL(fileURLWithPath: destinationPath as String)))
let player = AVPlayer(playerItem: playerItem)
playerController.player = player
player.play()
} else {
print ("error on getting movie data \(error?.localizedDescription)")
}
})
}
func doneButtonTapped(){
navigationController?.popViewControllerAnimated(true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Related
I want to load a 3d usdz blob into a view, but since I only have the data object, I'm trying to initialize the scene with that with no luck.
To that, I initialize the SCNSceneSource() and then open it using .scene().
Now what I don't understand:
If I use a URL and load the scene directly - it works.
If I use a Data object on the same URL it doesn't.
Apple docs says, the data should be of type NSData but that seems wrong.
import SceneKit
let url = URL(string: "file:///Users/thilo/Desktop/Input/UU2.usdz")!
// working
let src_ok = SCNSceneSource(url: url)
let scn_ok = src_ok?.scene(options: nil, statusHandler: {
a,b,c,d in print("OK: \(a) \(b) \(String(describing: c)) \(d) ")
})
print("Ok: \(scn_ok)")
// Not working?
let data = try! Data(contentsOf: url)
let src_bad = SCNSceneSource(data: data)
let scn_bad = src_bad?.scene(options: nil, status handler: {
a,b,c,d in print("BAD: \(a) \(b) \(String(describing: c)) \(d) ")
})
print("Failed: \(scn_bad)")
running on Playground says:
Ok: Optional(<SCNScene: 0x6000038e1200>)
BAD: 0.0 SCNSceneSourceStatus(rawValue: 4) nil 0x000000016fa948bf
BAD: 0.0 SCNSceneSourceStatus(rawValue: 4) nil 0x000000016fa942af
BAD: 0.0 SCNSceneSourceStatus(rawValue: -1) Optional(Error Domain=NSCocoaErrorDomain Code=260 "Could not load the scene" UserInfo={NSLocalizedDescription=Could not load the scene, NSLocalizedRecoverySuggestion=An error occurred while parsing the COLLADA file. Please check that it has not been corrupted.}) 0x000000016fa942af
Failed: nil
What am I missing?
SCNSceneSource doesn't support .usdz in Data context
Official documentation says that SCNSceneSource object supports only .scn, .dae and .abc file formats. But it turns out that SceneKit doesn't support URL-loading of .usdz only in the context of working with Data. Thus, when working with Data, use files in the .scn format.
import SceneKit
import Cocoa
class GameViewController : NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let url = URL(string: "file:///Users/swift/Desktop/ship.scn") {
let data = try! Data(contentsOf: url)
let source = SCNSceneSource(data: data)
let sceneView = self.view as! SCNView
sceneView.scene = source?.scene()
}
}
}
To load .usdz using URL, try SCNSceneSource.init?(url: URL)
class GameViewController : NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let url = URL(string: "file:///Users/swift/Desktop/ship.usdz") {
let source = SCNSceneSource(url: url)
let sceneView = self.view as! SCNView
sceneView.scene = source?.scene()
}
}
}
Or use SCNScene object to load .usdz model
class GameViewController : NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(fileURLWithPath: "/Users/swift/Desktop/ship.usdz")
do {
let scene = try SCNScene(url: url)
let sceneView = self.view as! SCNView
sceneView.scene = scene
sceneView.autoenablesDefaultLighting = true
} catch {
print(error.localizedDescription)
}
}
}
Gathering from the comment "does not support usdz" my solution is:
to create a temporary file ( .usdz) seems to be required by the API...
and then manually remove the temporary file after loading.
First extend FileManager with the below code:
public extension FileManager {
func temporaryFileURL(fileName: String = UUID().uuidString,ext: String) -> URL? {
return URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
.appendingPathComponent(fileName + ext)
}
}
For a limited hard-coded use case:
let fm = FileManager.default
let tempusdz = fm.temporaryFileURL(ext:".usdz")!
fm.createFile(atPath: tempusdz.path(), contents: sceneData)
let src = SCNSceneSource(url: tempusdz)
if let scene = src?.scene(options: nil) {
....
}
try? fm.removeItem(at: tempusdz)
of course this is a hack, because it will only work if the data is in usdz format.
Since usdz is a ZIP archive, maybe testing for a zip and then just doing the below is a better option:
let sceneData:Data? = data
var sceneSrc: SCNSceneSource? = nil
var tempURL:URL? = nil
if let dataStart = sceneData?.subdata(in: 0..<4),
let dataMagic = String(data: dataStart, encoding: String.Encoding.utf8) as String?,
dataMagic == "PK\u{3}\u{4}" {
let fm = FileManager.default
tempURL = fm.temporaryFileURL(ext: ".usdz")
if let tempURL {
fm.createFile(atPath: tempURL.path(), contents: sceneData)
sceneSrc = SCNSceneSource(url: tempURL)
}
} else {
sceneSrc = SCNSceneSource(data: sceneData!)
}
let scene = sceneSrc?.scene()
if let tempURL {
try? FileManager.default.removeItem(at: tempURL)
}
Does anyone knows a better solution?
Is there an easy way to check the type of the Data ?
potential solution could be to verify the format of the data object and ensure that it is a valid COLLADA file.
import Foundation
let url = URL(string: "file:///Users/thilo/Desktop/Input/UU2.usdz")!
let data = try! Data(contentsOf: url)
print("Data size: \(data.count)")
print("Data format: \(data.description)")
you usually get these types of errors when the data wasn't properly formatted
I want to download and play the sound from Google Translate (Text to Speech).
The mp3 file's downloaded successfull. I tried to play it with AVAudioPlayer, but there's no sound on both simulator and my real iPhone
I use XCode 10.2.1, Swift 5. Test on simulator (XSMax) and iPhone XSMax
import UIKit
import AVFoundation
class TextToSpeechGoogleTranslate: AVAudioPlayer, AVAudioPlayerDelegate {
var player: AVAudioPlayer?
func speak() {
let fileURL = URL(string: "https://translate.google.com/translate_tts?ie=UTF-8&total=1&idx=0&client=tw-ob&tl=vi&q=Hello" )!
let documentsUrl:URL = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL?)!
let destinationFileUrl = documentsUrl.appendingPathComponent("voice.mp3")
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url:fileURL)
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Successfully downloaded. Status code: \(statusCode)")
}
do {
try? FileManager.default.removeItem(at: destinationFileUrl)
try FileManager.default.copyItem(at: tempLocalUrl, to: destinationFileUrl)
print(destinationFileUrl)
do {
self.player = try AVAudioPlayer(contentsOf: destinationFileUrl)
self.player!.delegate = self
self.player!.prepareToPlay()
self.player!.volume = 1.0
self.player!.play()
}
catch let error as NSError {
print("Error: \(error.localizedDescription)")
}
catch {
print("AVAudioPlayer init failed")
}
} catch (let writeError) {
print("Error creating a file \(destinationFileUrl) : \(writeError)")
}
} else {
print("Error took place while downloading a file. Error description: %#", error?.localizedDescription as Any);
}
}
task.resume()
}
}
Output:
Successfully downloaded. Status code: 200
file:///Users/macbook/Library/Developer/CoreSimulator/Devices/F43F9B03-674C-4EE1-8CAD-01B5145868DE/data/Containers/Data/Application/6D1A3310-386D-4706-9F1E-DFF536B2A43F/Documents/voice.mp3
I played that file in Finder. It's OK.
I just tried your code and it seems to work fine. Maybe the problem is in how you create the TextToSpeechGoogleTranslate object and call speak() on it. For example, here is what I tried:
class ViewController: UIViewController {
var test: TextToSpeechGoogleTranslate?
override func viewDidLoad() {
super.viewDidLoad()
test = TextToSpeechGoogleTranslate()
test!.speak()
}
}
If that doesn't help, can you post some code showing how you call this method?
import UIKit
import AVFoundation
class ViewController: UIViewController, AVAudioPlayerDelegate {
var audioPlayer = TextToSpeechGoogleTranslate()
override func viewDidLoad() {
super.viewDidLoad()
audioPlayer.delegate = self
audioPlayer!.speak()
}
}
1) create instance of your class
2) Confirm it's delegate
3) call your function after you received your data
4) while testing from device, don't forget to turn off silence mode (volume up)
So i'm using this custom class to record my video -- https://github.com/piemonte/PBJVision. I am attempting to record video in my iOS app and I can't seem to get the code correct to upload the file to my parse server. A few things:
In the PBJVision class it allows you to use NSURL(fileWithPath:videoPath) to access the asset after the video has been recorded.
To access the Data in the asset and save to Parse, I use the following function:
func vision(vision: PBJVision, capturedVideo videoDict: [NSObject : AnyObject]?, error: NSError?) {
if error != nil {
print("Encountered error with video")
isVideo = false
} else {
let currentVideo = videoDict
let videoPath = currentVideo![PBJVisionVideoPathKey] as! String
print("The video path is: \(videoPath)")
self.player = Player()
self.player.delegate = self
self.player.view.frame = CGRect(x: cameraView.frame.origin.x, y: cameraView.frame.origin.y, width: cameraView.frame.width, height: cameraView.frame.height)
self.player.playbackLoops = true
videoUrl = NSURL(fileURLWithPath: videoPath)
self.player.setUrl(videoUrl)
self.cameraView.addSubview(self.player.view)
self.player.playFromBeginning()
nextButton.hidden = false
isVideo = true
let contents: NSData?
do {
contents = try NSData(contentsOfFile: videoPath, options: NSDataReadingOptions.DataReadingMappedAlways)
} catch _ {
contents = nil
}
print(contents)
let videoObject = PFObject(className: "EventChatroomMessages")
videoObject.setValue(user, forKey: "user")
videoObject.setValue("uG7v2KWBQm", forKey: "eventId")
videoObject.setValue(NSDate(), forKey: "timestamp")
let videoFile: PFFile?
do {
videoFile = try PFFile(name: randomAlphaNumericString(26) + ".mp4", data: contents!, contentType: "video/mp4")
print("VideoFile: \(videoFile)")
} catch _ {
print("error")
}
print(videoFile)
videoObject.setValue(videoFile, forKey: "image")
videoObject.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
if success == true {
ProgressHUD.showSuccess("Video Saved.", interaction: false)
dispatch_async(dispatch_get_main_queue()) {
ProgressHUD.dismiss()
}
} else {
ProgressHUD.showError("Error Saving Video.", interaction: false)
dispatch_async(dispatch_get_main_queue()) {
ProgressHUD.dismiss()
}
}
}
}
}
I am then using a UITableView to display my data from Parse. Here is how I retrieve my asset back from Parse and into my AVPlayer():
// Create Player for Reaction
let player = Player()
player.delegate = self
player.view.frame = CGRectMake(0.0, nameLabel.frame.origin.y + nameLabel.frame.size.height + 0.0, self.view.frame.width, 150)
player.view.backgroundColor = UIColor.whiteColor()
let video = message.objectForKey("image") as! PFFile
let urlFromParse = video.url!
print(urlFromParse)
let url = NSURL(fileURLWithPath: video.url!)
print(url)
let playerNew = AVPlayer(URL: url!)
let playerLayer = AVPlayerLayer(player: playerNew)
playerLayer.frame = CGRectMake(0.0, nameLabel.frame.origin.y + nameLabel.frame.size.height + 0.0, self.view.frame.width, 150)
cell.layer.addSublayer(playerLayer)
playerLayer.backgroundColor = UIColor.whiteColor().CGColor
playerNew.play()
I copy the value that is returned from urlFromParse which is (http://parlayapp.herokuapp.com/parse/files/smTrXDGZhlYQGh4BZcVvmZ2rYB9kA5EhPkGbj2R2/58c0648ae4ca9900f2d835feb77f165e_file.mp4) and paste it into my browser and the video plays in browser. Am I correct to assume the file has been saved correctly?
When I go to run my app, the video does not play.Any suggestion on what i'm doing wrong?
I have found that playing video using the pfFile.url does not work. You have to write the NSData from the PFFIle to a local file using the right extension (mov) and then play the video using the local file as the source.
When I click to play video, it shows like this. Wondering why. No error is shown. I did import AVKit and AVFoundation. Also, I tested in my device and simulator. Still does not work. Any suggestions? Thanks a lot.
**Print log**
//<PFFile: 0x79d472f0>
//https://files.parsetfss.com/d9cdffff-4a1f-4bd9-b6ee-e60c07d49236/tfss-7e1636b9-5701-4737-9fa6-10609aec89e8-adsVido
//<AVPlayer: 0x786f39b0>
// Click cell to play video.
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
if adsVideoArray[indexPath.row] != nil {
let theVideo:PFFile = adsVideoArray[indexPath.row]!
print(theVideo)
let url:NSURL = NSURL(string: theVideo.url!)!
theVideo.getDataInBackgroundWithBlock({ (data:NSData?, error: NSError?) -> Void in
if error == nil {
let player = AVPlayer(URL: url)
print(url)
print(player)
let playerController = AVPlayerViewController()
playerController.player = player
self.presentViewController(playerController, animated: true) {
player.play()
}
}
})
}
}
// load data from parse
func loadAdsFromParse() {
adsVideoArray.removeAll(keepCapacity: false)
let query = PFQuery(className: "user_ads")
query.findObjectsInBackgroundWithBlock({ (objects: [PFObject]?, error: NSError?) -> Void in
for object in objects! {
self.adsVideoArray.append(object.objectForKey("video") as? PFFile)
}
dispatch_async(dispatch_get_main_queue()) {
self.adsCollectionView.reloadData()
}
})
}
// the way to save video
self.tempVideo = info[UIImagePickerControllerMediaURL] as! NSURL!
let videoData = NSData(contentsOfFile:self.tempVideo.relativePath!)
let videoFile:PFFile = PFFile(name:"adsVido", data:videoData!)!
detailsObj["video"] = videoFile
detailsObj.saveInBackgroundWithBlock{ ... }
// I tried to play the video before upload it to Parse. It works.
func playImageTapped(pressed: UIGestureRecognizer) {
let player = AVPlayer(URL: tempVideo)
let playerController = AVPlayerViewController()
playerController.player = player
self.presentViewController(playerController, animated: true) {
player.play()
}
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
let mediaType = info[UIImagePickerControllerMediaType] as! NSString
self.tempVideo = videoAdded
Did you look and read the documentation on this...
https://parse.com/docs/osx/api/Classes/PFFile.html
Are you sure your saving your data as a movie and not perhaps as an image? a wild guess?
I seem to recall a new article saying parse.com is being shutdown, so maybe it not a good idea to use it anyway...
http://blog.parse.com/announcements/moving-on/
I am trying to download youtube video in my app and for that I used THIS and library included in that answer but my app is crashing with error :
fatal error: unexpectedly found nil while unwrapping an Optional value
Here is my swift code:
import UIKit
class ViewController: UIViewController {
let videoID = "hS5CfP8n_js"
var extractor = LBYouTubeExtractor()
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func start(sender: AnyObject) {
var url: String = "https://www.youtube.com/watch?v=hS5CfP8n_js"
let testURL = NSURL(string:url)
if testURL != nil {
extractor = LBYouTubeExtractor(URL: testURL, quality: LBYouTubeVideoQualityLarge)
extractor.extractVideoURLWithCompletionBlock({(videoURL, error) in
if error == nil {
println(videoURL)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
let data = NSData(contentsOfURL: videoURL)!
let pathToDocs = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! String
let fileName = "video_\(self.videoID).mp4"
let yourPath = pathToDocs.stringByAppendingPathComponent(fileName)
data.writeToFile(yourPath, atomically: true)
println("File \(fileName) successfully saved")
})
} else {
println("Failed extracting video URL using block due to error:\(error)")
}
})
}
}
}
my project is crashing at this line:
let data = NSData(contentsOfURL: videoURL)!
But I can get videoURL in console:
http%3A%2F%2Fr3---sn-tv0cgv5qc5oq-nu8e.googlevideo.com%2Fvideoplayback%3Fsver%3D3%26id%3D852e427cff27fe3b%26dur%3D55.681
... ty=small
I don't know what I am missing.
Here is my sample project.