Playing songs downloaded into the cache directory - swift

Downloading an mp3 and saving it into the Library/caches folder works, But accessing it later doesn't.
Checking if the song really exists, i opened up Finder and went to the simulator folders. and my folder looks like this:
My song is located at: Library/Caches/Digger/Flower.mp3
Trying to access this using AVPlayer:
let folderName = "Digger"
let fileName = "Flower.mp3"
let url = FileManager.default
.urls(for: .cachesDirectory, in: .userDomainMask)[0]
.appendingPathComponent(folderName).appendingPathComponent(fileName)
var urlString = url.absoluteString
print("MyPath: \(urlString)")
urlString.removeSubrange(urlString.range(of: "file://")!)
let player = AVPlayer(url: URL(string: urlString)!)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
self.present(playerViewController, animated: true) {
player.play()
}
But it doesn't work.
Simulator screenshot upon runtime:

Your URL handling is incorrect.
Replace these lines:
var urlString = url.absoluteString
print("MyPath: \(urlString)")
urlString.removeSubrange(urlString.range(of: "file://")!)
let player = AVPlayer(url: URL(string: urlString)!)
with:
let player = AVPlayer(url: url)
url already is the URL that you need. No need to try to convert it in any way.
For reference, use url.path to convert a file URL into a path string. And if you need to create a URL from a path string, use URL(fileURLWithPath:), not URL(string:). Only use URL(string:) with a string that has a URL scheme such as file://, https://, etc.

Related

Get video in Phpicker in swiftui, but can't play by Videoplayer

I'm trying to load a video from Phpicker and preview it by Video player, but I don't know why it doesn't work.
So I did some research, and most of the solutions put videos in the project file and use Bundle.main.url(forResource:, withExtension:) to play video.
Here's my picker to pick a video. I'm trying to get the video file URL and make a copy using FileManager.default.copyItem(at:, to:) then use URL in Avplayer.
(Pick a video from the photo library)
DispatchQueue.main.async {
guard let videoURL = videoURL as? URL else { return }
debugPrint("video url: \(videoURL.absoluteString)")
let fileID = UUID().uuidString
let fileName = "\(fileID).mp4"
debugPrint("file name: \(fileName)")
let newURL = URL(fileURLWithPath: NSTemporaryDirectory() + fileName)
debugPrint("new url: \(newURL.absoluteString)")
try? FileManager.default.copyItem(at: videoURL, to: newURL)
self.parent.video = newURL
}
(User new URL to play video)
if let url = providerRoomSummitViewModel.roomIntroVideoURL {
VStack {
VideoPlayer(player: AVPlayer(url: URL))
}
}

Swift - Check if the file download from a URL is completed

I am downloading mp4 clips from a M3U8 manifest which can have around 700 clips. Everything works fine to download them but what would be the best to check individual downloads are finished? When all the clips are downloaded, I merge them into one but I need to know when all my clips have been downloaded first.
This is the code snippet I use to download the video clip.
func download(video: String){
DispatchQueue.global(qos: .background).async {
if let url = URL(string: "http://SERVER/storage/sessions/SESSIONID/mp4_segments/\(video)"),
let urlData = NSData(contentsOf: url) {
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0];
let fileName = video
let filePath = "\(documentsPath)/SegmentVideos/\(fileName)"
urlData.write(toFile: filePath, atomically: true)
}
}
}
This is the code snippet that reads the M3U8 file and splits it so I can grab the video clip's name.
func checkM3U8forClips(){
guard let url = url else {return}
do {
let contents = try String(contentsOf: url)
let splitContent = contents.components(separatedBy: "\n")
for split in splitContent {
if split.hasSuffix("mp4") {
download(video: split)
}
}
} catch {
print("error with mp4 segments: \(error.localizedDescription)")
}
}
One reason you are in a quandary is that this code is wrong:
if let url = URL(string: "http://SERVER/storage/sessions/SESSIONID/mp4_segments/\(video)"),
let urlData = NSData(contentsOf: url) {
You must never use NSData(contentsOf:) to do networking. If you want to network, then network: use URLSession and a proper data task or download task. Now you get the callbacks you need; if you do it in the full form you get a full set of delegate callbacks that tell you exactly when a download has succeeded and completed (or failed).
As for your overall question, i.e. how can I know when multiple asynchronous operations have all finished, that is what things like DispatchGroup, or operation dependencies, or the new Combine framework are for.

Load temp video and play

I need to play continuously a video stored inside the temp directory.
func setVideo(url vid: String!) {
let directory = NSTemporaryDirectory()
let tempURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("tempMovie\(Date().timeIntervalSince1970)").appendingPathExtension("mp4")
let tempFile = NSURL.fileURL(withPathComponents: [directory])
let file = vid.components(separatedBy: ".")
guard let path = Bundle.main.path(forResource: "\(tempURL)", ofType:file[2]) else {
debugPrint( "\(file.joined(separator: ".")) not found with path: \(file[0] + "." + file[1])")
return
}
let player = AVPlayer(url: URL(fileURLWithPath: path))
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.view.bounds
self.videoView.layer.addSublayer(playerLayer)
player.play()
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.player.currentItem, queue: nil, using: { (_) in
DispatchQueue.main.async {
self.player?.seek(to: kCMTimeZero)
self.player?.play()
}
})
}
The video location and file url is:
file:///private/var/mobile/Containers/Data/Application/E7D12401-F147-4905-83BE-72909F91E004/tmp/tempMovie1501672791.33525.mp4
The continuous loop part works from a previous project however I can't seem to get the temp stored video to load inside the player. I've tried to get the temp directory manually but it didn't work.
Moving the video from tmp to Documents directory worked for me.
This is how you do it:
let documentsDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let destinationURL = documentsDirectoryURL.appendingPathComponent("filename.fileExtension")
do {
try FileManager.default.moveItem(at: previousTempLocation, to: destinationURL)
} catch let error as NSError { print(error.localizedDescription)}
let player = AVPlayer(url: destinationURL)
Apple says this on /tmp directory
Use this directory to write temporary files that do not need to
persist between launches of your app. Your app should remove files
from this directory when they are no longer needed; however, the
system may purge this directory when your app is not running.
Apple also recommends this for user data
Put user data in Documents/. User data generally includes any files
you might want to expose to the user—anything you might want the user
to create, import, delete or edit. Video and audio apps may even include
files that the user has downloaded to watch or listen to later.

Extract Audio from Video in Swift

I'm new to Swift from Objective-C and have hit a wall. I need to extract audio from a .mov file so I can then run this through speech to text processing. The code I have so far creates a file but it does not contain audio. This is the code I have so far
private var audioFilePath: URL!
private func createAudioFile(){
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = NSURL(fileURLWithPath: path)
self.audioFilePath = url.appendingPathComponent("myAudio.m4a")
let theAsset = AVAsset(url: self.outputFilePath)
let exporter = AVAssetExportSession(asset: theAsset, presetName: AVAssetExportPresetHighestQuality)
exporter?.outputURL = self.audioFilePath
exporter?.outputFileType = AVFileTypeMPEG4
print("file path = ", self.audioFilePath)
exporter?.exportAsynchronously(completionHandler: {
print("exporter?.status\(exporter?.status)")
})
}
Thanks for your help!

Load Url image from Plist in Swift

I have a plist running in my project which contains number of image urls.I am trying to pass the url image to my imageView which is in the same viewController.I found similar questions from github like Loading/Downloading image from URL on Swift , Swift - Read plist , Can't get plist URL in Swift I went through all those answers but no luck so far rather than fatal crash. My partial codes as follows....
Method 1:
override func viewDidLoad() {
super.viewDidLoad()
if let path = Bundle.main.path(forResource: "apps", ofType: "plist"),
let root = (NSArray(contentsOfFile: path))
{
let url = NSURL(string: path)
let data = NSData(contentsOf: url! as URL)
if let imageData = data {
imageView.image = UIImage(data: imageData as Data)
}
**// Swift Console Printinging as Follows**
print(root)
// printing all my plist url links like {icon = "https://xxxxxxxxxxx.com/image/girl_face_freckles_eyes_92358_1920x1080.jpg";}
print(url)
// (/Users/xxxxxx/Library/Developer/CoreSimulator/Devices/52DA3F73-83E0-4C29-9DE1-D8D5F0731C13/data/Containers/Bundle/Applica ... ps.plist)
print(data)
// nil
print(imageView.image)
//nil
} else {
print("Either the file does not exist or the root object is an array")
}
Method 2:
let path = Bundle.main.path(forResource: "apps", ofType: "plist")
let url = NSURL(string: path!)
let imgData = try? Data(contentsOf: url as! URL)
let img = UIImage(data: imgData!)!
print(img) // fatal crash
}
path is the path to your plist, not to your image URL.
The image URL is in the key "icon" in the "root" array.
Get the first item of the array and subscript with the key, you should get your image URL:
if let item = root[0] as? [String:Any] {
if let result = item["icon"] as? String {
print(result)
}
}
The path in (NS)Bundle is a file system path and these paths must be created with URL(fileURLWithPath:)
let path = Bundle.main.path(forResource: "apps", ofType: "plist")
let url = URL(fileURLWithPath: path!)
But why does nobody use the URL related API which is much more convenient
let url = Bundle.main.url(forResource: "apps", withExtension: "plist")