How to stream a video from an https url in objective-c - objective-c

I'm trying to make a screensaver for Mac, that streams a simple mp4 video from a backend server. I'm not used to coding for Mac or in Objective-c, so I'm pretty lost. Until now I've figured out how to load a video locally, but when I try to switch out the file URL to an HTTPS URL it doesn't seem to work. This is how I'm loading the local mp4 file right now:
NSURL *url = [[NSBundle bundleForClass:MainScript.class] URLForResource: #"video" withExtension: #"mp4"];
AVAsset *asset = [AVAsset assetWithURL: url];
NSArray *assetKeys = #[#"playable", #"hasProtectedContent"];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset: asset automaticallyLoadedAssetKeys:assetKeys];
NSKeyValueObservingOptions options = NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew;
VideoPlayer *player = [VideoPlayer playerWithPlayerItem: playerItem];
[playerItem addObserver: player forKeyPath: #"status" options: options context: PlayerItemContext];
player.volume = 0;
As I said before, I'm not used to code in Objective-c, so I'm not 100 percent certain that this is the code that creates the local video stream, but I'm pretty sure it is.
What I want to do from here, is to replace the file URL: #"video" with an HTTPS URL like this: #"https://example.com/video". Everything is working on the backend server, so my problem is only how to load the video on a Mac screensaver.
As I understand it, I'm using AVPlayerItem to load the video right now. I don't know if there is a better way to do it, but if there is, please tell me :)

Try using VLCKit, I'm pretty sure that can do HTTPS out of the box.

Related

App Transport Security issue with AVAudioPlayer loading local file objective-C, XCode 9

I have an app which loads a bundled m4a audio file as a local resource and it has worked well for many years now. I'm updating the app to iOS 11.3/XCode 9.3 and it is now failing on iPad (works on iPhone) when I press my play button:
2018-05-13 20:45:24.437626-0700 my app[6175:218735] App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.
2018-05-13 20:45:24.437791-0700 my app[6175:218735] Cannot start load of Task <117E064E-ABB3-45F2-8D64-76397B140092>.<0> since it does not conform to ATS policy
2018-05-13 20:45:24.437948-0700 my app[6175:218732] NSURLConnection finished with error - code -1022
My code:
NSURL *medURL = [[NSBundle mainBundle] URLForResource: #"MyFile"
withExtension: #"m4a"];
NSError *playerError = nil;
AVAudioPlayer *newPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL: medURL error: &playerError];
self.appSoundPlayer = newPlayer;
// "Preparing to play" attaches to the audio hardware and ensures that playback
// starts quickly when the user taps Play
[appSoundPlayer prepareToPlay];
It fails on the prepareToPlay.
Reading other answers I've found a lot of information on the ATS error so I added this to my plist file for the app, cleaned, rebuilt and ran it -- but I am still getting the error:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
<key>NSAllowsArbitraryLoadsForMedia</key>
<true/>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSAllowsArbitraryLoadsInWebContent</key>
<true/>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/> </dict>
I can't use the NSExceptionDomain key because this is a local resource, there is no domain. How can I fix this?
Note: to be clear, this file is bundled into the app and has not been downloaded at any time. This is not an "http" url, it is a "file:" url.
Update: Looking at the CFNetworkLog I realized that there was a NSURLConnection to the network happening just before the audio prep in AppDelegate. When I removed this call everything started working. Since both work independently, it appears to me that there is some sort of conflict in NSURLConnection which is happening -- possibly a bug in the API? It would be good to know what the proper work-around is for this, I have gotten it to work well enough by removing the prepareToPlay call above -- this is not ideal as it takes longer when the user goes to play the audio file.
Another update: the app started working today for no apparent reason, I hadn't changed anything since the last time I tried it. Unfortunately I can't determine if fixes work at this point! To be sure, it does appear that my plist keys are working in my iOS 11.3 simulator.
In my project is using AVAudioPlayer to play audio, and my source code:
NSString *soundFilePath = [[NSBundle mainBundle] pathForResource:#"MyFile" ofType:#"m4a"];
NSURL *soundFileURL = [NSURL fileURLWithPath:soundFilePath];
AVAudioPlayer* audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFileURL error:nil];
audioPlayer.numberOfLoops = -1; //Infinite
[audioPlayer setVolume:1];
[audioPlayer play];

Objective-C RSS Reader - XML reading error

I am new to obj-C and i'm trying to make an easy RSS Reader. It works with an external RSS file but not when i load the same file in my server based on AWS services.
This is the snippet code:
[super viewDidLoad];
feeds = [[NSMutableArray alloc] init];
NSURL *url = [NSURL URLWithString:#"http://images.apple.com/main/rss/hotnews/hotnews.rss"];
parser = [[NSXMLParser alloc] initWithContentsOfURL:url];
[parser setDelegate:self];
[parser setShouldResolveExternalEntities:NO];
[parser parse];
If i read that rss via web, i see this:
When I upload that file on my server, i see this:
I tried to avoid manual upload via Filezilla but using wget command and i also tried ascii mode transfer, but still not work. RSS Reader show an empty list.
It is the problem with URL. I had the crash with the same code with error
Unable to read data
I replaced code with
feedURL = NSURL(string:"http://images.apple.com/main/rss/hotnews/hotnews.rss#sthash.fuVEonEt.dpuf")!
parser = XMLParser(contentsOf:feedURL as URL)!
parser.delegate = self
parser.parse()
It work fine now.
As suggested by #Larme, it was the App Transport Security Issue How do I load an HTTP URL with App Transport Security enabled in iOS 9? .
This solved my issue!

How to load the next file while current one is playing through HTTP Live Streaming?

I'm working on an app in which I need to stream a large collection of audio files ranging from 5 to 15 seconds each.
I would like to reduce the load time between the files as much as possible.
Main Question:
Is there a way to start buffering the next file (through HLS) while the current one is playing the last segment?
Is AVQueuePlayer an appropriate solution for this on the iOS side?
A detailed explanation will be much appreciated, since I am new both to HTTP Live Streaming and the AV Foundation.
Related Question:
How do radio apps stream their audio with no lag between the songs?
Yes, AVQueuePlayer is an appropriate solution for playing a sequence of audio streamed from the internet via HTTP protocol.
I'm using AVQueuePlayer for quite a while now with excellent results and no lagging between songs. Here is a simple example on how to use AVQueuePlayer:
NSURL *url1 = [NSURL URLWithString:[urlsToPlay objectAtIndex:0]];
NSURL *url2 = [NSURL URLWithString:[urlsToPlay objectAtIndex:1]];
NSURL *url3 = [NSURL URLWithString:[urlsToPlay objectAtIndex:2]];
self.item1 = [[AVPlayerItem alloc] initWithURL:url1];
self.item2 = [[AVPlayerItem alloc] initWithURL:url2];
self.item3 = [[AVPlayerItem alloc] initWithURL:url3];
self.radioPlayerURLs = [[NSArray alloc] initWithObjects:self.item1,self.item2, self.item3, nil];
self.onDemandPlayer = [AVQueuePlayer queuePlayerWithItems:self.radioPlayerURLs];
[self.onDemandPlayer play];
For more details, please consulte Apple documentation:
AVFoundation
AVQueuePlayer

Recording only AUDIO using AVCaptureSession

I would like to record audio using AVCaptureSession, and Audio only (without video). I followed RosyWriter sample (removing all code concerning video), but when I create my AsseWriter for audio I have an error with hte line :
assetWriter = [[AVAssetWriter alloc] initWithURL:captureURL fileType:(NSString*)kUTTypeAUdio error:&error];
the error is : Invalide file type UTI...
captureURL is as following :
NSURL captureURL = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#%#", NSTemporaryDirectory(), #"captureTest.mp3"]];
Do you know how to fix it ? I tried with kUTTypeMP3 or kUTTypeMPEG4Audio but nothing change.
I try to use only audio, because I need to do a AVCaptureSession and separate audio from video to send them separatly to a server.
I'm interested in any sample of code that can help me.
Thanks for you help
kUTTypeAudio is too vague - try something like AVFileTypeWAVE or AVFileTypeAppleM4A.
p.s. iOS doesn't have an mp3 encoder (it does have a decoder)

How to make QTMovie play file from URL with forced (MP3) type?

I'm using QTKit to progressively download and play an MP3 from a URL. According to this documentation, this is the code I should use to accomplish that:
NSURL *mp3URL = [NSURL URLWithString:#"http://foo.com/bar.mp3"];
NSError *error = nil;
QTMovie *sound = [[QTMovie alloc] initWithURL:mp3URL error:&error];
[sound play];
This works, and does exactly what I want — the MP3 URL is lazily downloaded and starts playing immediately. However, if the URL does not have the ".mp3" path extension, it fails:
NSURL *mp3URL = [NSURL URLWithString:#"http://foo.com/bar"];
NSError *error = nil;
QTMovie *sound = [[QTMovie alloc] initWithURL:mp3URL error:&error];
[sound play];
No error is given, no exception is raised; the duration of the sound is just set to zero, and nothing plays.
The only way I have found to work around this is to force a type by loading the data manually and using a QTDataReference:
NSURL *mp3URL = [NSURL URLWithString:#"http://foo.com/bar"];
NSData *mp3Data = [NSData dataWithContentsOfURL:mp3URL];
QTDataReference *dataReference =
[QTDataReference dataReferenceWithReferenceToData:mp3Data
name:#"bar.mp3"
MIMEType:nil];
NSError *error = nil;
QTMovie *sound = [[QTMovie alloc] initWithDataReference:dataReference error:&error];
[sound play];
However, this forces me to completely download ALL of the MP3 synchronously before I can start playing it, which is obviously undesirable. Is there any way around this?
Thanks.
Edit
Actually, it seems that the path extension has nothing to do with it; the Content-Type is simply not being set in the HTTP header. Even so, the latter code works and the former does not. Anyone know of a way to fix this, without having access to the server?
Edit 2
Anyone? I can't find information about this anywhere, and Google frustratingly now shows this page as the top result for most of my queries...
Two ideas. (The first one being a bit hacky):
To work around the missing content type, you could embed a small Cocoa webserver that supplements the missing header field and route your NSURL over that "proxy".
Some Cocoa http server implementations:
http://code.google.com/p/cocoahttpserver/
http://cocoawithlove.com/2009/07/simple-extensible-http-server-in-cocoa.html
http://culturedcode.com/cocoa/
The second one would be, to switch to a lower level framework (From QTKit to AudioToolbox).
You'd need more code, but there are some very good resources out there on how to stream mp3 using AudioToolbox.
e.g.:
http://cocoawithlove.com/2008/09/streaming-and-playing-live-mp3-stream.html
Personally I'd go with the second option. AudioToolbox isn't as straightforward as QTKit but it offers a clean solution to your problem. It's also available on both - iOS and Mac OS - so you will find plenty of information.
Update:
Did you try to use another initializer? e.g.
+ (id)movieWithAttributes:(NSDictionary *)attributes error:(NSError **)errorPtr
You can insert your URL for the key QTMovieURLAttribute and maybe you can compensate the missing content type by providing other attributes in that dictionary.
This open source project has a QTMovie category that contains methods to accomplish similar things:
http://vidnik.googlecode.com/svn-history/r63/trunk/Source/Categories/QTMovie+Async.m
If you thought weichsel's first solution was hacky, you're going to love this one:
The culprit is the Content-Type header, as you have determined. Had QTKit.framework used Objective-C internally, this would be a trivial matter of overriding -[NSHTTPURLResponse allHeaderFields] with a category of your choosing. However, QTKit.framework (for better or worse) uses Core Foundation (and Core Services) internally. These are both C-based frameworks and there is no elegant way of overriding functions in C.
That said, there is a method, just not a pretty one. Function interposition is even documented by Apple, but seems to be a bit behind the times, compared to the remainder of their documentation.
In essence, you want something along the following lines:
typedef struct interpose_s {
void *new_func;
void *orig_func;
} interpose_t;
CFStringRef myCFHTTPMessageCopyHeaderFieldValue (
CFHTTPMessageRef message,
CFStringRef headerField
);
static const interpose_t interposers[] __attribute__ ((section("__DATA, __interpose"))) = {
{ (void *)myCFHTTPMessageCopyHeaderFieldValue, (void *)CFHTTPMessageCopyHeaderFieldValue }
};
CFStringRef myCFHTTPMessageCopyHeaderFieldValue (
CFHTTPMessageRef message,
CFStringRef headerField
) {
if (CFStringCompare(headerField, CFSTR("Content-Type"), 0) == kCFCompareEqualTo) {
return CFSTR("audio/x-mpeg");
} else {
return CFHTTPMessageCopyHeaderFieldValue(message, headerField);
}
}
You might want to add logic specific to your application in terms of handling the Content-Type field lest your application break in weird and wonderful ways when every HTTP request is determined to be an audio file.
Try replacing http:// with icy://.
Just create an instance like this...
QTMovie *aPlayer = [QTMovie movieWithAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
fileUrl, QTMovieURLAttribute,
[NSNumber numberWithBool:YES], QTMovieOpenForPlaybackAttribute,
/*[NSNumber numberWithBool:YES], QTMovieOpenAsyncOKAttribute,*/
nil] error:error];