Cache videos in tableview - ios

I want to cache videos that are displayed in a tableview. However, I am not sure what to cache. I'm using AVFoundation, in particular I'm using AVPlayer and creating AVPlayerItem's.
My question is: what do I cache? Is it the AVPlayer, AVPlayerItem, or the underlying asset of AVPlayerItem called the AVAsset?
Please give a code sample (or library) with answer. Thanks!

You need to have private var mediaCache = NSMutableDictionary() for caching videos.
How to cache:
let assetForCache = AVAsset(url: URL(string: cell.videoRef)!)
self.mediaCache.setObject(assetForCache, forKey: cacheKey as NSCopying)
How to use: Just check if object exists in cache for this row. If yes - use it, no - download it

Cache means download the video of each cell and try to use downloaded video for the second time . Either you can Temp location or Document directory.
Execute a download call to download the video from URL Video Link.
In your cellForRowat indexpath check whether video available in your local if its there play.or else stream for the first time , simultaneously place a download
call for that video in background .So that for second time you can use local video and play without internet .

Related

Why can't I play certain .m3u8 items with AVPlayer?

I'm working on a project where I have an instance of AVPlayer capable of playing different audio content that I retrieve from a backend, from podcast to music and streamings. Every content has two types of urls: one with mp3 and another with a m3u8 file. All the mp3 files work good. However some m3u8 files work fine and others don't. In particular, those who don't work cause the AVPlayer to crash with the error:
Error Domain=AVFoundationErrorDomain Code=-11819 "Cannot Complete Action"
UserInfo={NSLocalizedRecoverySuggestion=Try again later.,
NSLocalizedDescription=Cannot Complete Action.}
I don't understand what the problem is. According to this answer it is a wrong Manifest file, which in my case is - for example - the following:
#EXTM3U
#EXT-X-MEDIA:TYPE=AUDIO,URI="_64/index.m3u8",GROUP-ID="2#48000-64000",NAME="AAC 64",DEFAULT=NO,AUTOSELECT=NO
#EXT-X-MEDIA:TYPE=AUDIO,URI="_80/index.m3u8",GROUP-ID="2#48000-80000",NAME="AAC 80",DEFAULT=NO,AUTOSELECT=NO
#EXT-X-MEDIA:TYPE=AUDIO,URI="_96/index.m3u8",GROUP-ID="2#48000-96000",NAME="AAC 96",DEFAULT=NO,AUTOSELECT=NO
#EXT-X-STREAM-INF:BANDWIDTH=133336,CODECS="mp4a.40.2",AUDIO="2#48000-96000"
_96/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=100641,CODECS="mp4a.40.2",AUDIO="2#48000-64000"
_64/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=116989,CODECS="mp4a.40.2",AUDIO="2#48000-80000"
_80/index.m3u8
On the Apple forum, I found this answer which says iOS 14+ is on fault. Unfortunately I cannot test with an iOS 13 physical device.
Do you have any suggestion?
Tested on Xcode 13.1 with iPhone 7plus with iOS 15.0.2.
Finally I found a solution for this issue. What worked for me was this. I believe the problem was that my manifest files were structured like the following:
#EXT-X-MEDIA:TYPE=AUDIO,URI="_64/index.m3u8", GROUP-ID="1#48000-64000",NAME="Audio 64",DEFAULT=NO,AUTOSELECT=NO
In particular they had DEFAULT=NO,AUTOSELECT=NO. Therefore before calling replaceCurrentItem I now do the following:
let asset = AVAsset(url: url)
let playerItem = AVPlayerItem(asset: asset)
for characteristic in asset.availableMediaCharacteristicsWithMediaSelectionOptions {
if let group = asset.mediaSelectionGroup(forMediaCharacteristic: AVMediaCharacteristic.audible) {
if let option = group.options.first {
playerItem.select(option, in: group)
}
}
}
This makes all my HLS audio playable by the AVPlayer.
I dont see version in your .m3u8. Try adding #EXT-X-VERSION:03 into your playlist. AVPlayer does need to have version included in playlist (Android EXO player does not need it). Here is example of playlist that might work:
#EXTM3U
#EXT-X-VERSION:03
#EXT-X-MEDIA:TYPE=AUDIO,URI="_64/index.m3u8",GROUP-ID="2#48000-64000",NAME="AAC 64",DEFAULT=NO,AUTOSELECT=NO
#EXT-X-MEDIA:TYPE=AUDIO,URI="_80/index.m3u8",GROUP-ID="2#48000-80000",NAME="AAC 80",DEFAULT=NO,AUTOSELECT=NO
#EXT-X-MEDIA:TYPE=AUDIO,URI="_96/index.m3u8",GROUP-ID="2#48000-96000",NAME="AAC 96",DEFAULT=NO,AUTOSELECT=NO
#EXT-X-STREAM-INF:BANDWIDTH=133336,CODECS="mp4a.40.2",AUDIO="2#48000-96000"
_96/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=100641,CODECS="mp4a.40.2",AUDIO="2#48000-64000"
_64/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=116989,CODECS="mp4a.40.2",AUDIO="2#48000-80000"
_80/index.m3u8

How can you obtain a current manifest from the playhead in AV player?

I am currently trying to capture the currently playing HLS/DASH manifest in the AV player.
Would anyone have any code snippets/documentation that could help with it?
To access the current url that the avplayer is streaming from:
if let currAsset = player.currentItem.asset as? AVURLAsset {
var url = currAsset.url
... use the url ...
}
I didn't find any official documentation about this either. I suppose then at the time of writing that AVPlayer does not provide the manifest.
Failing that, consider downloading the playlist and parse the manifest by your own.
I found M3U8Kit useful to parse m3u8 manifest.

Saving and retrieving videos and their thumbnails to the app's directory

In my app, user's are allowed to record videos. After they record videos they are appended into an array, and placed on a table view with their thumbnails so they can select the video and watch it. However, if i record a video and it goes on the table view, after i exit out of the app the video is gone.
What is the best way to save the video, without saving it to the users camera roll, and retrieve it later when they open the app again. I read about core data but heard it was not recommended. Is there a better way to do so?
Can someone please explain this to me, I am very bad dealing with directories. I barely have any experience with them.
I record a video to this file location here:
self.movieFileOutput.startRecording(toOutputFileURL: URL(fileURLWithPath:self.videoFileLocation()), recordingDelegate: self)
func videoFileLocation() -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .long
dateFormatter.timeStyle = .long
let date = dateFormatter.string(from: Date())
return NSTemporaryDirectory().appending("videoFile\(date)")
}
Once the video is finished recording, I append it to this array.
var videosArray: [URL] = [URL]()
Then i get the thumbnail of each video using this method:
func getThumbnail(_ outputFileURL:URL) -> UIImage {
let clip = AVURLAsset(url: outputFileURL)
let imgGenerator = AVAssetImageGenerator(asset: clip)
let cgImage = try! imgGenerator.copyCGImage(
at: CMTimeMake(0, 1), actualTime: nil)
let uiImage = UIImage(cgImage: cgImage)
return uiImage
}
I then append it to this array here:
var thumbnails = [UIImage]()
How can i make these videos and their thumbnails not disappear when a user exits the app. I really need help as I am very frustrated.
Would the best way to be to save them to:
User Defaults
CoreData
Retrieve them straight from the directory
Or Any other suggestion you may have.
Can you tell me the best way and show me how to do this. I needed a detailed answer. Thank you.
I would recommend storing all the metadata for the recording in core-data, and all of the video and image data in the file system. In core-data store any meta-data that you may want to display in the tableview - length, title, creator, creation-date, modified-date, tag etc - even if it could be generated by loading the file - it is much faster to just load it from a database. Store the video and thumbnail in the documents directory (not the temporary directory) and store a RELATIVE path to the files in core-data. Make sure to use a relative and not a absolute path, as the app directory can change when the app is upgraded or restored via iTunes.
It seems that you are naming the files based on the creation date of the video. I would recommend against this. Date formatters are complex and can change based on locale or timezone or other system settings. I would recommend instead to name the file based on a UUID that you store in core-data.
To summarize:
Store your files in the documents directory - not the temporary directory
Store all your files meta-data in core data.
Save a relative path to the video file and thumbnail file in core-data
Name your files with a UUID - not a date string

Videos recorded from app has incorrect creation date from PHAsset

I have an app that records video and displays them in a certain order. The videos recorded in my app has the correct date, but the times were all the same. So, all of the video recorded today show: 2015-07-31 13:15:51 +0000
I'm not setting any properties related to time in my capture session or movie output. I can't seem to find any documentation on how to do this properly. Does anyone have an idea?
Thanks!
Update: I recorded more video within the app. Turns out that the date is also wrong. It’s creation date reads the same as all the other videos created previously.
For kicks, I delete the app from my phone, recorded a new video. It has the correct day and time. But after recording a second video, the date and time is the same as the previous recorded video.
It turns out that I was writing over the same file path. Creating a unique string each time solved my problem.
let uuid = NSUUID().UUIDString
let outputPath: NSString = "\(NSTemporaryDirectory()) + \(uuid) + output.mov"
let outputURL = NSURL(fileURLWithPath: outputPath as String)
movieFileOutput?.startRecordingToOutputFileURL(outputURL, recordingDelegate: self)

Playing audio files continually using audio recorded url

I am newcomer in Objective-C and have experience only 12 months in iPhone development.
I am recording audio files in One UIViewController, and playing on another UIViewController. For playing purpose i am saving the date string for generation of url,it is fine working properly,
But ,now my problem is i want to play previous audio record file for some time after that i want to play next audio file using url . i am saving all the data using nsuser dafaults please help me
NSM![enter image description here][1]utableArray *dateString;
NSURL recordFile = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingString:[self.dateString objectAtIndex:sender.tag]]];
For playing
player = [[AVAudioPlayer alloc] initWithContentsOfURL:recordFile error:&error];
from fig when i click a tag 2 i want to play first 5 sec tag1 after that i want to play tag2
Record the audio -> Save the audio in the Temp -> Track the saved path of the audio file (You can store this saved path in an array) . Repeat the same steps for the next file.
Use AVQueuePlayerfor playing items one after the other.

Resources