Why is AVURLAsset Not Loading the File? - ios

OK. I have a nasty feeling that this will be met with the gentle chirp of crickets...
I base that on this and this.
I'm actually wondering if this is a feature, not a bug, as maybe there's a security issue with loading a movie locally, then playing it. I would think that isn't the case, but maybe. It should be noted that the loaded asset comes from a REST interaction with a server, in which the movie data is actually just a part of a data query response. It is not something that is loaded directly from a video streaming page (it is SSL, though).
I'm pretty green at AV Foundation.
I have the following code:
do {
// We create a path to a unique temporary file to grab the media.
let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString)
// Store the media in the temp file.
try myData.write(to: url, options: .atomic)
let options = [AVURLAssetPreferPreciseDurationAndTimingKey: true]
let asset = AVURLAsset(url: url, options: options)
if 0 < asset.tracks.count {
print("YOU GET \(asset.tracks.count) TRACKS!")
} else {
print("NO TRACKS FOR YOU!")
}
} catch let error {
NSLog("Error Encoding AV Media: %#", error._domain)
}
Pretty basic, eh? The "myData" variable contains a MP4 movie (.m4v) that was downloaded. I write it to a temp file, then load that temp file with AVURLAsset, just like it says to do.
The problem is that I can never get the dangblammit movie to play. The file is where it's supposed to be. I can fish out the temp file, slap on a '.m4v' extension, and play it in the QT Viewer.
I am quite prepared to accept a slap upside the head, followed by "ya darn eedjut!", but I'd like to know which "M" I should "RTFM".

The problem seems to be with this line
let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString)
You should add the extension for the file.
let videoName = UUID().uuidString + ".mp4"
let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(videoName)
Hope it fix.

Related

download youtube video offline swift

I'm trying to download a youtube video on the phone so the user can later play it offline. I hooked a button to where the user can download. The code of how I'm currently downloading the video is here below.
#objc func downloadSelectedVideo() {
if let audioUrl = URL(string: "http://freetone.org/ring/stan/iPhone_5-Alarm.mp3") {
// create your document folder url
let documentsUrl = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
// your destination file url
let destination = documentsUrl.appendingPathComponent(audioUrl.lastPathComponent)
print(destination)
// check if it exists before downloading it
if FileManager().fileExists(atPath: destination.path) {
print("The file already exists at path")
} else {
// if the file doesn't exist
// just download the data from your url
URLSession.shared.downloadTask(with: audioUrl, completionHandler: { (location, response, error) in
// after downloading your data you need to save it to your destination url
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("audio"),
let location = location, error == nil
else { return }
do {
try FileManager.default.moveItem(at: location, to: destination)
print("file saved")
} catch {
print(error)
}
}).resume()
}
}
}
as you can see I have hooked up the URL to a free music which is in mp3 and it works fine I can download the music and everything works just fine, however when I try to hook up a YouTube video then it never gets to the print statement file saved I tried this as the URL if let audioUrl = URL(string: "https://www.youtube.com/watch?v=3WSgJCYIewM")
but the print statement file saved never ran, but when I tried with the other I mentioned earlier it print file saved.
What URL should use to download the youtube videos, and do I have to use an mp3 or mp4 source to download the videos. I'm trying not to use any third-party sites if you can come up with any solution it would be great and helpful. Thanks
YouTube spells this out pretty specifically that you cannot do this.
YouTube API Services - Developer Policies
Found this under E. Handling YouTube Data and Content
You and your API Clients must not, and must not encourage, enable, or
require others to:
download, import, backup, cache, or store copies of YouTube
audiovisual content without YouTube's prior written approval,
IANAL, but I'd rather not go up against their legal team.
YouTube does not provide a simple URL that taps into the video or audio directly. Instead, you would have to extract thier link using some algorithm. If you'd like to achieve that I would recommend you use XCDYouTubeKit as it is well maintained and easy to use. However, If you'd like a ready made app, I have developed an open source application - YouTag - that you can simply install on your iOS device and use directly without having to go through developing one.

How do I grab the UTI or MIME Type from a video asset fetched using PHAsset.fetchAsset?

I've run into an issue where I can't seem to figure out a good way of grabbing the UTI or MIME type from a video asset after fetching it from the photo library.
let assets = PHAsset.fetchAssets(in: someCollection, options: videosOnly)
(iterating over each asset in assets...)
PHImageManager.default().requestAVAsset(forVideo: asset, options: nil) { (AVAsset: avAsset, AVAudioMix: avAudioMix, info) in
(I'm trying to find the UTI/MIME type here...)
}
One iffy solution I found was to grab the pathExtension of the file by casting the avAsset as a AVURLAsset:
guard let avURLAsset = avAsset as? AVURLAsset else { return }
let videoExt = avURLAsset.url.pathExtension
This seems to get the corresponding filetype ('m4v', 'mov', 'mp4') in basic test cases, but I'm worried that this is not a robust enough solution. I saw another post (Finding image type from NSData or UIImage) that details grabbing the image type by looking at the bytes of NSData, but did not touch on video files.
I have also tried the solution suggested at How to get MIME type for image or video in iOS 8 using PHAsset?:
let requestContentEditingOptions = PHContentEditingInputRequestOptions()
asset.requestContentEditingInput(with: requestContentEditingOptions) { (contentEditingInput, contentEditingInfo) in
guard let videoUTI = contentEditingInput?.uniformTypeIdentifier else { completion(nil); return }
}
Unfortunately, the uniformTypeIdentifier property came up as nil when I attempted this. If anyone sees a potential problem with my implementation of this, I'd love to hear it.
Has anyone else run into this before? Would love to hear any ideas or if anyone things the pathExtension is a reasonable option for a production app.

Saved file using AudioKit AKOfflineRenderNode not right

I am using AudioKit to mix WAV files together with MIDI files.
I also need to save the result in a separate file.
To mix the WAVs and MIDIs I am using an AKMIDISampler with an AKSequencer like this:
func add(track: MixerTrack) -> Bool {
do{
let trackSampler = AKMIDISampler()
try trackSampler.loadWav(track.instrument.fileName)
trackSampler.connect(to: mixer)
let sequencer = AKSequencer(filename: track.midi.fileName)
sequencer.setTempo(Double(tempo))
sequencer.setRate(rate)
sequencer.setGlobalMIDIOutput(trackSampler.midiIn)
sequencer.enableLooping()
sequencer.enableLooping()
sequencers.append(sequencer)
tracks.append(track)
return true
} catch {
return false
}
}
I am using the SongProcessor example from AudioKit's examples for ideas on how to use AKOfflineRenderNode.
The thing is the example works with AKAudioPlayer instances and not sequencers as I am using. I believe I cannot use players because I need to mix the WAV and MIDI files, and I was only able to achieve that using sequencers.
My first question is: Is it possible to create files from sequencers the same way it is done in SongProcessor with players?
I was able to save an m4a file but the result is weird. First, if I don't set the rate manually to a number like 40, it is veeery slow to play all the notes. And when I set ti to a value like that,I can hear the sequence playing but at wrong rates. At some moments the beats play correctly but they often start playing too slow or too fast at different times.
Is there something I am doing wrong? Is this a bug with AKOfflineRenderNode or is it just not mean to be used like this?
Here is the code I use to save the mix to disk:
func saveMixToDisk() -> URL? {
do {
let fileManager = FileManager.default
let name = UUID().uuidString.appending(".m4a")
let documentDirectory = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create:false)
let fileURL = documentDirectory.appendingPathComponent(name)
offlineRender.internalRenderEnabled = false
let duration = sequencers.first!.length.seconds
for sequencer in sequencers {
sequencer.stop()
sequencer.setTime(AKDuration(seconds: 0).musicTimeStamp)
sequencer.rewind()
}
for sequencer in sequencers {
sequencer.setRate(40) // I would like to find a way to avoid having to set this, since this value is hardcoded and I don't know how to find the correct one. (When I only play through the sequencer inside the app the rate is perfect, but it gets messed up when rendering to URL)
sequencer.play()
}
try offlineRender.renderToURL(fileURL, seconds: duration * 10)
for sequencer in sequencers {
sequencer.stop()
sequencer.setTime(AKDuration(seconds: 0).musicTimeStamp)
sequencer.rewind()
}
offlineRender.internalRenderEnabled = true
return fileURL
} catch let error {
print(error)
return nil
}
}
Any help is very much appreciated. I can't seem to be able to get this to work, and sadly I don't know of any other options in iOS to achieve what I need.
Instead of using AKOfflineRender, try the new AudioKit.renderToFile in AudioKit 4.0.4: https://github.com/AudioKit/AudioKit/commit/09aedf7c119a399ab00026ddfb91ae6778570176
I think you need to use this method in iOS11
[AudioKit renderToFile:file duration:self->_audioDurationSeconds error:&error prerender:^{
[self.voicePlayer start];
}];

Get local URL of downloaded data instead of providing server url

I would like to play a video file in my ViewController which is loaded in every page of my PageViewController. As you will be able to see I use a plugin called Carlos to cache the videos (which initially need to be downloaded from a server) so that they do not have to be downloaded every time the user hits a new page. However, I can't figure a way out how to play this downloaded file (NSData). Thus, I would really like to know how I can get the URL of the downloaded file so that I can play it using AVPlayer.
Code (still using URL from server)
let omniCache = videoCache.cache
let request = omniCache.get(URL(string: video!)!)
request
.onSuccess { videoFile in
print("The file..." )
print(videoFile)
//How can I get the local URL here instead of my server url
if let videoURL = URL(string: self.video!){
if self.player == nil {
let playerItemToBePlayed = AVPlayerItem(url: videoURL as URL)
self.player = AVPlayer(playerItem: playerItemToBePlayed)
let playerLayer = AVPlayerLayer(player: self.player)
playerLayer.frame = self.view.frame
self.controlsContainerView.layer.insertSublayer(playerLayer, at: 0)
}
}
}
.onFailure { error in
print("An error occurred :( \(error)")
}
Look at this code of yours:
videoFile in
print("The file..." )
print(videoFile)
if let videoURL = URL(string: self.video!){
So in the first line you print videoFile, which turns out to be the data of the file. But then you ignore it! You never mention videoFile again. Why do you ignore it? That is the data, you already have the data. Now play it!
If the data is a file, get its file URL and play it. If it is in memory — it definitely should not be, because a video held entirely in memory would crash your program — save it, and get that file URL and play it.
[I have to ask, however, why you are interposing this cache plug-in between yourself and such a simple task. Why don't you just download the remote video to disk, yourself?]

How to loop over multiple (audio/mp3) files in a directory with swift? - Swift, iOS8 Soundboard AVPlayer

I am trying to create a soundboard using Swift to fiddle around and learn about swift/iOS8.
This is what I have done in my tableViewController as of now:
var soundPath = NSBundle.mainBundle().pathForResource("RandomSound", ofType: "m4a")
var soundURL = NSURL.fileURLWithPath(soundPath!)
self.audioPlayer = AVAudioPlayer(contentsOfURL: soundURL, error: nil)
self.audioPlayer.play()
This works to play the sound as expected. Now in order to have a few more sounds and display them in tableView, I created an array of Sound class which looks like this:
class Sound {
var name: [String]
var fileURL = NSURL()
// more fields as required
}
and then using it like this:
var sounds: [Sound] = []
sounds.name = "RandomSound"
sounds.fileURL = soundURL!
However, I want something to make it to scale better. Lets say I have a bunch of mp3 files in one folder (for example 100).
In this case I would like some mechanism, with which, I can simply point the directory and/or filetype and then the remaining heavy lifting like going through each file in the directory and finding the name/artist/and other information would be extracted from mp3 file ID3 tags (or something similar).
If not all details, at least populating the name/URL field and looping over all files in a directory should be possible to automate, but I'm unable to understand what I should be looking at to achieve this.
EDIT
This part allows me scan all files in a directory of a given filetype(s). In my case I am looking for mp3. This is what I've come up with:
soundArray: [soundElement] = []
let fileManager = NSFileManager.defaultManager()
let directoryPath = "/path/to/directory/containing/my/sound/files"
let enumerator:NSDirectoryEnumerator = fileManager.enumeratorAtPath(directoryPath)!
while let element = enumerator.nextObject() as? String {
if element.hasSuffix("mp3") { // checks the extension
// DO my sound path stuff here
//println(element)
let soundURL = NSURL.fileURLWithPath(directoryPath + element)
//Of course I'll not be playing them directly here, (I don't see why) but if I needed to ...
//self.audioPlayer = AVAudioPlayer(contentsOfURL: soundURL, error: nil)
//self.audioPlayer.play()
//Instead it just makes sense to populate some array/dict containing all the sounfURLs
soundElement.name = String(element)
soundElement.fileURL = soundURL
self.soundArray.append(soundElement)
}
}
class soundElement {
var name = " "
var fileURL = NSURL()
}
I am still not clear on how to get ID3 information from mp3 files in swift. I have referred the AVAsset information at https://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVAsset_Class/index.html#//apple_ref/occ/cl/AVAsset but it is not clear how to use it yet.
I looked at the AVAsset (https://developer.apple.com/reference/avfoundation/avasset#//apple_ref/occ/cl/AVAsset) docs and saw that there is a metadata property on it (https://developer.apple.com/reference/avfoundation/avasset/1386884-metadata).
It is an array of AVMetadataItems (https://developer.apple.com/reference/avfoundation/avmetadataitem) so you could check the items if they contain the metadata you are looking for
(Keys are usually something like AVMetadataiTunesMetadataKeySongName in the key space AVMetadataKeySpaceiTunes or AVMetadataID3MetadataKeyAlbumTitle in the AVMetadataKeySpaceID3.
If you want an easier solution, you could check out the ID3Edit library from this guy: https://github.com/philiphardy/ID3Edit
I'd try the following:
Load your songs as AVAsset
Iterate over the metadata array of each song
Print out the keys and values of the items to identify the ones you are interested in
Once you are familiar with the structure of the metadata, you can start retrieving the ones you want.
Hope this helps

Resources