I am a newbie to swift and tvOS.I want to download a video from the url and display it in the ViewController. while i try with the ios Devices the video is downloaded and works fine.But with the tvOS the Video is not downloaded.
why is it so..?
How can i download and play the video in the Apple TV.
Here is my Code
let backgroundSessionConfiguration = URLSessionConfiguration.background(withIdentifier: "backgroundSession")
backgroundSession = Foundation.URLSession(configuration: backgroundSessionConfiguration, delegate: self, delegateQueue: OperationQueue.main)
let url = URL(string: "myURl")!
downloadTask = backgroundSession.downloadTask(with: url)
downloadTask.resume()
func urlSession(_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL){
let path = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
let documentDirectoryPath:String = path[0]
let fileManager = FileManager()
let destinationURLForFile = URL(fileURLWithPath: documentDirectoryPath.appendingFormat("/file.mp4"))
if fileManager.fileExists(atPath: destinationURLForFile.path){
showFileWithPath(path: destinationURLForFile.path)
}
else{
do {
try fileManager.moveItem(at: location, to: destinationURLForFile)
// show file
showFileWithPath(path: destinationURLForFile.path)
}catch{
print("An error occurred while moving file to destination url")
}
}
}
There are some restrictions in tvOS regarding Local storage
Your app can only access 500 KB of persistent storage that is local to the device (using the NSUserDefaults class). Outside of this limited local storage, all other data must be purgeable by the operating system when space is low...
You can take a look to this documentation for further details:
https://developer.apple.com/library/content/documentation/General/Conceptual/AppleTV_PG/
Related
I am able to play the sound from one URL. But takes nearly 10 seconds to play the audio.
#IBAction func playBtnTap(_ sender: Any) {
let url = URL(string: muscUrl!)!
let playerItem = AVPlayerItem(url: url)
self.player = AVPlayer(playerItem: playerItem)
self.player!.play()
}
I am new to iOS. I am not sure, how can I download the URL audio and then play that audio? I don't want that 10 sec delay to play audio.
Update, I tried :
override func viewDidLoad() {
super.viewDidLoad()
let urlstring = "http://radio.spainmedia.es/wp-content/uploads/2015/12/tailtoddle_lo4.mp3"
let url = NSURL(string: urlstring)
print("the url = \(url!)")
downloadFileFromURL(url: url!)
}
func downloadFileFromURL(url:NSURL){
var downloadTask:URLSessionDownloadTask
var request = URLRequest(url:url as URL)
downloadTask = URLSession.shared.downloadTask(with: request, completionHandler: { (URL, response, error) -> Void in
self.play(url: URL as! NSURL)
})
downloadTask.resume()
}
func play(url:NSURL) {
print("Playing \(url)")
do {
self.player = try AVAudioPlayer(contentsOf: url as URL)
player!.prepareToPlay()
player!.volume = 1.0
player!.play()
} catch let error as NSError {
//self.player = nil
print(error.localizedDescription)
} catch {
print("AVAudioPlayer init failed")
}
}
getting error in console :
The operation couldn’t be completed. (OSStatus error 1954115647.)
in line self.player = try AVAudioPlayer(contentsOf: url as URL)
Your first code is correct. I don't experience any significant delay when I run it on my device. Naturally it can take some time on a slow Internet connection to fill the buffer before playback can start; but your second code, downloading and then playing, would therefore be even slower.
If you want to know what's happening with the AVPlayer's buffer, use KVO to track changes in its timeControlStatus.
In Swift 4, I can't figure out how to obtain audio metadata from the document picker when importing audio files and from the media picker when picking a media item from the user's media library. I am currently converting the url from both import methods to an AVAudioPlayer item. Can someone please let me know a method, even if I have to code it separately for both the mediaPicker and the documentPicker?
func mediaPicker(_ mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection) {
for song in mediaItemCollection.items as [MPMediaItem] {
// AVAudioPlayer
let url = song.value(forProperty: MPMediaItemPropertyAssetURL) as? NSURL
audioPlayer = try? AVAudioPlayer(contentsOf: url! as URL)
audioPlayer.prepareToPlay()
audioPlayer.play()
}
mediaPicker.dismiss(animated: true, completion: nil)
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
guard controller.documentPickerMode == .import else { return }
let fileURL = url
let url = url.deletingPathExtension()
audioPlayer = try? AVAudioPlayer(contentsOf: fileURL)
audioPlayer.enableRate = true
audioPlayer.prepareToPlay()
audioPlayer.play()
updateCurrentTrack()
}
Isn't it just a question of following thru the docs?
Use the URL to form an AVAsset.
https://developer.apple.com/documentation/avfoundation/avurlasset/1385698-init
Now extract the metadata.
https://developer.apple.com/documentation/avfoundation/avasset/1390498-commonmetadata
Now get the desired metadata items:
https://developer.apple.com/documentation/avfoundation/avmetadataitem/1385843-metadataitems
You now have one or more AVMetadataItem objects. To retrieve a value, use asynchronous key-value loading:
https://developer.apple.com/documentation/avfoundation/avasynchronouskeyvalueloading
I triggered a background download of an image. It succeeds - location is a path to the actual image. I get no error message, but also the image does NOT show up in the photos app. I have set the NSPhotoLibraryUsageDescription Info.plist key. The app has rights to access the photos. I know, this code triggers another background thread, but that shouldn't be a problem, because the "location" file is still there after "didFinishDownloadingTo" finished. Is there anything else to take care of when storing JPG files to the camera roll?
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
PHPhotoLibrary.shared().performChanges({
print("starting")
let req = PHAssetCreationRequest.forAsset()
req.isFavorite = true;
let options = PHAssetResourceCreationOptions();
options.shouldMoveFile = true;
req.addResource(with: PHAssetResourceType.photo, fileURL: location, options: options)
})
}
You're using addResource incorrectly. What you have is not a resource; it's the image. So first, load the data from the URL and turn it into an image:
if let url = location, let d = try? Data(contentsOf:url) {
let im = UIImage(data:d)
}
Now just add the image as an asset:
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAsset(from: im!)
})
One you've persuaded yourself that that works, you can start dressing it up.
I'm trying to implement an offline mode to a streaming application.
The goal is to be able to download an HLS stream on the device of the user to make it possible to watch the stream even while the user is offline.
I have recently stumble on this tutorial.
It seems to answer the exact requirements of what I was trying to implement but I'm facing a problem while trying to make it work.
I've created a little DownloadManager to apply the logic of the tutorial.
Here is my singleton class:
import AVFoundation
class DownloadManager:NSObject {
static var shared = DownloadManager()
private var config: URLSessionConfiguration!
private var downloadSession: AVAssetDownloadURLSession!
override private init() {
super.init()
config = URLSessionConfiguration.background(withIdentifier: "\(Bundle.main.bundleIdentifier!).background")
downloadSession = AVAssetDownloadURLSession(configuration: config, assetDownloadDelegate: self, delegateQueue: OperationQueue.main)
}
func setupAssetDownload(_ url: URL) {
let options = [AVURLAssetAllowsCellularAccessKey: false]
let asset = AVURLAsset(url: url, options: options)
// Create new AVAssetDownloadTask for the desired asset
let downloadTask = downloadSession.makeAssetDownloadTask(asset: asset,
assetTitle: "Test Download",
assetArtworkData: nil,
options: nil)
// Start task and begin download
downloadTask?.resume()
}
func restorePendingDownloads() {
// Grab all the pending tasks associated with the downloadSession
downloadSession.getAllTasks { tasksArray in
// For each task, restore the state in the app
for task in tasksArray {
guard let downloadTask = task as? AVAssetDownloadTask else { break }
// Restore asset, progress indicators, state, etc...
let asset = downloadTask.urlAsset
downloadTask.resume()
}
}
}
func playOfflineAsset() -> AVURLAsset? {
guard let assetPath = UserDefaults.standard.value(forKey: "assetPath") as? String else {
// Present Error: No offline version of this asset available
return nil
}
let baseURL = URL(fileURLWithPath: NSHomeDirectory())
let assetURL = baseURL.appendingPathComponent(assetPath)
let asset = AVURLAsset(url: assetURL)
if let cache = asset.assetCache, cache.isPlayableOffline {
return asset
// Set up player item and player and begin playback
} else {
return nil
// Present Error: No playable version of this asset exists offline
}
}
func getPath() -> String {
return UserDefaults.standard.value(forKey: "assetPath") as? String ?? ""
}
func deleteOfflineAsset() {
do {
let userDefaults = UserDefaults.standard
if let assetPath = userDefaults.value(forKey: "assetPath") as? String {
let baseURL = URL(fileURLWithPath: NSHomeDirectory())
let assetURL = baseURL.appendingPathComponent(assetPath)
try FileManager.default.removeItem(at: assetURL)
userDefaults.removeObject(forKey: "assetPath")
}
} catch {
print("An error occured deleting offline asset: \(error)")
}
}
}
extension DownloadManager: AVAssetDownloadDelegate {
func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didLoad timeRange: CMTimeRange, totalTimeRangesLoaded loadedTimeRanges: [NSValue], timeRangeExpectedToLoad: CMTimeRange) {
var percentComplete = 0.0
// Iterate through the loaded time ranges
for value in loadedTimeRanges {
// Unwrap the CMTimeRange from the NSValue
let loadedTimeRange = value.timeRangeValue
// Calculate the percentage of the total expected asset duration
percentComplete += loadedTimeRange.duration.seconds / timeRangeExpectedToLoad.duration.seconds
}
percentComplete *= 100
debugPrint("Progress \( assetDownloadTask) \(percentComplete)")
let params = ["percent": percentComplete]
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "completion"), object: nil, userInfo: params)
// Update UI state: post notification, update KVO state, invoke callback, etc.
}
func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didFinishDownloadingTo location: URL) {
// Do not move the asset from the download location
UserDefaults.standard.set(location.relativePath, forKey: "assetPath")
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
debugPrint("Download finished: \(location)")
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
debugPrint("Task completed: \(task), error: \(String(describing: error))")
guard error == nil else { return }
guard let task = task as? AVAssetDownloadTask else { return }
print("DOWNLOAD: FINISHED")
}
}
My problem comes when I try to call my setupAssetDownload function.
Everytime time I try to resume a downloadTask I get an error message in the urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) delegate function.
The log of the message is:
Task completed: <__NSCFBackgroundAVAssetDownloadTask:
0x7ff57fc024a0>{ taskIdentifier: 1 }, error: Optional(Error
Domain=AVFoundationErrorDomain Code=-11800 \"The operation could not
be completed\" UserInfo={NSLocalizedFailureReason=An unknown error
occurred (-12780), NSLocalizedDescription=The operation could not be
completed})
To give you all the relevant information the URL I past to my setupAssetDownload function is of type
URL(string: "https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8")!
I been looking for a cause and solution for this error but I don't seem to be able to find one for the time being.
I would be very grateful for any tips or any clues on how resolve this issue or any indication of errors in my singleton implementation that could explain this behaviour.
Thank you in advance.
Martin
EDIT:
It seems that this bug occurs on a simulator. I launch my app on a real device and the download started without any problem. Hope this helps. Still don't understand why I cannot try this behaviour on a simulator.
In an app I'm writing, I need to download a PDF from a server and display it in a UIWebView. To this end, I've got a bit of code that retrieves the PDF from an endpoint (it's not a URL, and on a desktop computer, opens up a dialogue for saving as in a browser) and loads it onto the device by grabbing the data in a class called PDFGrabber:
func getPDF(completionHandler:#escaping (URL) -> Void)
{
let theURL:String = "https://mywebsite.com/Endpoint"
let fileURL:URL = URL(string: theURL)!
var request = URLRequest(url: fileURL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0)
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: {data, response, error -> Void in
var documentURL = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)).last
documentURL = documentURL?.appendingPathComponent("MyDocument.pdf")
do
{
try data?.write(to: documentURL!, options: .atomic)
completionHandler(documentURL!)
}
catch
{
print(error)
}
})
task.resume()
}
Then, in the table view controller showing the PDFs (let's call it PDFTableView, I can use the documentURL from the PDFGrabber when a PDF is requested (by tapping a cell in the table):
PDFGrabber.getPDF(){fileURL -> () in
DispatchQueue.main.async
{
if let resultController = self.storyboard!.instantiateViewController(withIdentifier: "PDFViewer") as? PDFViewer
{
resultController.thePDFPath = stringURL
self.present(resultController, animated: true, completion: nil)
}
}
Finally, I have another View Controller with a UIWebView called "WebView" and the attribute "thePDFPath" as a string in the PDFViewer view controller. In the viewDidLoad() method, I can say:
override func viewDidLoad()
{
let pathToPDF:URL = URL(string: thePDFPath)!
WebView.loadRequest(URLRequest(url: pathToPDF))
}
Together, this loads the PDF into the web view. However, the loading times can be a little slow, and I'd like to be able to calculate how much of the PDF has been loaded onto a user's device using a progress bar and a string. From other questions, I gather than I'd need to have my PDFGrabber class extend URLSessionDownloadDelegate, and then implement the functions. To get the amount of bytes downloaded is straightforward, since I can simply go:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten writ: Int64, totalBytesExpectedToWrite exp: Int64)
{
percentDownloaded = (Float(writ)/Float(exp)) * 100
print("Progress: " + String(describing: percentDownloaded))
}
But I'd also like to be able to open the PDFView only after the PDF is wholly downloaded after displaying its progress as a percent (I've got an overlay view that does this). Before, I could use a completion handler and wait until the PDF finished downloading, then open it.
However, this method does not allow me to access the amount of bytes downloaded; would I go about opening the view from the
urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL)
function, or is there something else I need to do?
Suggestions would be much appreciated; thanks!
import WebKit
here you can load your pdf in your application
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
if let pdfURL = Bundle.main.url(forResource: "food-and-drink-menu-trafalgar-tavern", withExtension: "pdf", subdirectory: nil, localization: nil) {
do {
let data = try Data(contentsOf: pdfURL)
let webView = WKWebView(frame: CGRect(x:0,y:NavigationView.frame.size.height,width:view.frame.size.width, height:view.frame.size.height-NavigationView.frame.size.height))
webView.load(data, mimeType: "application/pdf", characterEncodingName:"", baseURL: pdfURL.deletingLastPathComponent())
view.addSubview(webView)
}
catch {
// catch errors here
}
}
}