I am currently building an http server in iPhone that allows wifi speakers to download music from it. This is so far what I have done:
This is the part that initializes the http server:
- (void)startHttpServer
{
[DDLog addLogger:[DDTTYLogger sharedInstance]];
_httpServer = [[HTTPServer alloc] init];
[_httpServer setType:#"_http._tcp."];
NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
[_httpServer setDocumentRoot:documentPath];
[_httpServer setPort:9088];
[_httpServer setName:#"Music Server"];
[self startServer]; }
This is the part that gets the asset URL of the music file:
if (currentGroupIndex < currentGroupPlayList.count)
{
[self.savedIndexInfo setObject:[NSNumber numberWithInteger:currentGroupIndex] forKey:groupIDNum];
//makes sure that there is no existing duplicated url;
NSURL *oneURL = [self.savedURLInfo objectForKey:groupIDNum];
[[NSFileManager defaultManager] removeItemAtURL:oneURL error:nil];
MPMediaItem *music = [currentGroupPlayList objectAtIndex:currentGroupIndex];
NSURL *assetURL = [music valueForProperty:MPMediaItemPropertyAssetURL];
[self exportAssetAtURL:assetURL
withTitle:[NSString stringWithFormat:#"song_%lld_%ld", groupID, (long)currentGroupIndex]
groupAdress:groupIP
groupID:groupID
songDuration:[[music valueForProperty:MPMediaItemPropertyPlaybackDuration]intValue]];
}
This is the part that imports the music file and exports the output url:
- (void)exportAssetAtURL:(NSURL*)assetURL withTitle:(NSString*)title groupAdress:(NSString *)groupAdress groupID:(UInt64)groupID songDuration:(NSInteger)duration
{
NSString *ext = [TSLibraryImport extensionForAssetURL:assetURL];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSURL *outURL = [[NSURL fileURLWithPath:[documentsDirectory stringByAppendingPathComponent:title]] URLByAppendingPathExtension:ext];
// we're responsible for making sure the destination url doesn't already exist
[[NSFileManager defaultManager] removeItemAtURL:outURL error:nil];
// create the import object
TSLibraryImport* import = [[TSLibraryImport alloc] init];
[import importAsset:assetURL toURL:outURL completionBlock:^(TSLibraryImport* import)
{
if (import.status == AVAssetExportSessionStatusCompleted)
{
NSString *musicURL = [NSString stringWithFormat:#"http://%#:9088/%#.%#", [self currentIPAddress], title, ext];
NSLog(#"URL: %#", musicURL);
cntl_http_pb_pkt pb;
pb.header.packet_type = CONTROL_PACKET;
pb.header.op = CNTL_PB_HTTP_PLAY;
pb.header.body_len = sizeof(pb.body);
const char *adressChar = [groupAdress cStringUsingEncoding:NSASCIIStringEncoding];
pb.body.group_addr = inet_addr(adressChar);
pb.body.group_port = htons(INIT_GROUP_PORT);
pb.body.seq = [NSDate timeIntervalSinceReferenceDate];
self.playSeq = pb.body.seq;
const char *urlChar = [musicURL cStringUsingEncoding:NSASCIIStringEncoding];
memcpy(pb.body.url, urlChar, 200);
NSData *data = [NSData dataWithBytes:&pb length:sizeof(pb)];
[self.savedURLInfo setObject:outURL forKey:[NSNumber numberWithUnsignedLongLong:groupID]];
[self.savedPlayData setObject:musicURL forKey:[NSNumber numberWithUnsignedLongLong:groupID]];
[self performSelectorOnMainThread:#selector(sendUdpData:) withObject:data waitUntilDone:YES];
_acting = NO;
}
else
{
_acting = NO;
NSLog(#"Error importing: %#", import.error);
}
import = nil;
}];
}
My Problem:
50% of the time, its either the music file cannot be downloaded from the server, or the download speed is extremely slow (less than 10kb/s). My problem is I don't know what are the possible causes of this problem, how am I able to solve this.
Different Instances:
Sometimes at the beginning, the download speed is ok(download speed is at 200+kb/s) and then without doing anything at all, the speed would suddenly drop, making the speakers totally impossible to fetch the file from the server. Take note, there are no other devices connected to the router, only speakers and the phone.
Sometimes at the beginning, the song was not playing, and then after a couple of secs the playing would begin.
There is one time I tried connecting 8 speakers to one router, the playing was smooth and fine. But then after an hour or less, Problems would occur, like a lot of stops would occur.
Solutions I have tried:
Tried connecting all speakers under different types of routers
(TP-LINK, Tenda, Netgear, mi, etc...). above instances still occurs.
When the song stops playing or when the data received is incredibly slow, I tried relaunching the app, restarting and phone, and even restarting the router, but then speed is still very slow at the beginning.
Tried disconnecting the router from modem.
Tried connecting only four devices and one phone only.
5.Tried turning off all other wifi devices that are not even under the same network.
Tried changing the port of the http server.
When the problem occurs, I tried checking whether the connection is gone, or the http server is down. But both are ok.
Take Note:
I tried accessing the url by using a browser, the download speed was as slow as the time the speaker was not playing the song smoothly. So I guess the problem is not on the speaker.
There are no error messages returned at all when the problem occurs
No connection terminated when checked on NSLog.
My Request:
So please, if you guys have an idea of what might cause this problem please tell me, or the better, if you know the solution to this. Its really hard to determine what the problem is. Because no error messages are reflected, and I tried removing a lot things that could become possible cause to this problem, but still no good result.
Any help would be highly appreciated.
Related
My app involves creating and recording sounds you make within the app. I have tried multiple ways of recording like AVAudioRecord. So far these only offer a way to record through the microphone but I want to record the actual sounds made within the app. How on earth do I achieve this. I've been on this for days, can someone please help me, thanks!
I also use Audio unit for playback in my application.
This is what I have so far using AVAudioRecord:
_audioController = [[AEAudioController alloc] initWithAudioDescription:[AEAudioController nonInterleavedFloatStereoAudioDescription] inputEnabled:YES];
- (void)beginRecording {
// Init recorder
self.recorder = [[AERecorder alloc] initWithAudioController:_audioController];
NSString *documentsFolder = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)
objectAtIndex:0];
NSString *filePath = [documentsFolder stringByAppendingPathComponent:#"Myaudio.aiff"];
NSLog(#"%#", filePath);
// Start the recording process
NSError *error = NULL;
if ( ![_recorder beginRecordingToFileAtPath:filePath
fileType:kAudioFileAIFFType
error:&error] ) {
// Report error
return;
}
// Receive both audio input and audio output. Note that if you're using
// AEPlaythroughChannel, mentioned above, you may not need to receive the input again.
[_audioController addInputReceiver:_recorder];
[_audioController addOutputReceiver:_recorder];
}
I have this strange issue that I am having trouble resolving. I am creating an App which allows music to be played back. When the screen is locked (and there is a currently playing song), the lock screen will populate with a bunch of data. One piece is the album art.
The problem is that after the phone is locked and I skip a few tracks (forwards or backwards), the UIImages are no longer being loaded. If I test out the functionality and quickly skip forward and backwards in my playback queue, the album art will appear for the first 4-5 songs. After that, the images stop appearing because I get a NSFileReadNoPermissionError from my code that grabs the image. I understand that I apparently do not have permission to access the png image files, but I do not understand why. My application created them, saved them on disk, and is now trying to load them from disk while my app is running in a background state.
The relevant code snippet:
+ (void)updateLockScreenInfoAndArtForSong:(Song *)song
{
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *artDirPath = [documentsPath stringByAppendingPathComponent:#"Album Art"];
NSString *path = artDirPath;
//-----> LIST ALL FILES for debugging <-----//
NSLog(#"LISTING ALL FILES FOUND");
int count;
NSArray *directoryContent = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path
error:NULL];
for (count = 0; count < (int)[directoryContent count]; count++)
{
NSLog(#"File %d: %#", (count + 1), [directoryContent objectAtIndex:count]);
}
//-----> END DEBUG CODE <-----//
Song *nowPlayingSong = [MusicPlaybackController nowPlayingSong];
Class playingInfoCenter = NSClassFromString(#"MPNowPlayingInfoCenter");
if (playingInfoCenter) {
NSMutableDictionary *songInfo = [[NSMutableDictionary alloc] init];
NSError *error;
NSData *data = [NSData dataWithContentsOfURL:
[AlbumArtUtilities albumArtFileNameToNSURL:nowPlayingSong.albumArtFileName] options:NSDataReadingUncached error:&error];
NSInteger code = error.code;
NSLog(#"Error code: %li", (long)code); //prints 257 sometimes, which is NSFileReadNoPermissionError
UIImage *albumArtImage = [UIImage imageWithData:data];
if(albumArtImage == nil){ //song has no album art, check if its album does
Album *songsAlbum = song.album;
if(songsAlbum){
albumArtImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:
[AlbumArtUtilities albumArtFileNameToNSURL:songsAlbum.albumArtFileName]]];
}
}
[songInfo setObject:nowPlayingSong.songName forKey:MPMediaItemPropertyTitle];
NSInteger duration = [nowPlayingSong.duration integerValue];
[songInfo setObject:[NSNumber numberWithInteger:duration]
forKey:MPMediaItemPropertyPlaybackDuration];
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:songInfo];
}
}
Any help would be immensely appreciated! I have tried so many thing that I am at a loss for what to even try next. Note the above code snippet is called in
- (void)remoteControlReceivedWithEvent:(UIEvent *)event
when event.subtype is UIEventSubtypeRemoteControlNextTrack or UIEventSubtypeRemoteControlPreviousTrack.
Figured it out after trying everything all day lol. Turns out that by default in iOS 8, much of the system is encrypted (files cannot be accessed after the phone is locked)...with a small delay of course. This is why a few album art images were loading but they stopped working after a few seconds. The delay between when the phone locked and the encryption enabled itself made it seem like my issue was "random".
Anyway for anyone reading this, my solution involved setting the file protection of all folders and subfolder and files leading to the album art directory.
Hint:
[attributes setValue:NSFileProtectionCompleteUntilFirstUserAuthentication forKey:NSFileProtectionKey];
Set that attribute on a file when it is being created (provide the information as an NSDictionary). If an existing directory or file is to be modified, gather the attributes of the thing using NSFileManager, and then set the values as shown above.
I have an app that uses AVAudioPlayer to play sound effects, music, etc. It works fine for just about every user (100,000+ users), but I've had several reports of sound not playing at all even though sound effects are working on other apps on the same device. The bug has gone away for some users after an app restart/device reboot, but for others it continues to persist. AVSpeechSynthesizer isn't working for these users either.
I haven't been able to reproduce the bug to see if the AVAudioPlayer is throwing an error, so I'm not sure if it is - but I don't see why it would be working fine for 99.9% of users and not this tiny subset.
Has anyone else run into something like this?
Here's the code I'm using to set up the player:
[[AVAudioSession sharedInstance] setCategory:#"AVAudioSessionCategoryAmbient" error:nil];
NSString *file = [filename stringByDeletingPathExtension];
NSString *extension = [filename pathExtension];
NSURL *soundFileURL = [[NSBundle mainBundle] URLForResource:file withExtension:extension];
self.soundPlayer1 = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFileURL error:&error];
[self.soundPlayer1 prepareToPlay];
I have code that records the screen in my app, and then I want to be able to play it back. I have gotten so far as to get to the black loading screen with the rewind, play and fast forward buttons, but it hangs at loading without playing anything.
This code plays the clip:
-(IBAction)playMovie:(id)sender
{
MPMoviePlayerViewController *movieViewController = [[MPMoviePlayerViewController alloc] initWithContentURL:locationOfVideoURL];
movieViewController.moviePlayer.movieSourceType = MPMovieSourceTypeFile;
[self presentMoviePlayerViewControllerAnimated:movieViewController];
}
I believe the problem has something to do with the locationOfVideoURL variable. I am able to print it and it seems like it SHOULD work.
Here it is printed out:
file://localhost/Users/myusername/Library/Application%20Support/iPhone%20Simulator/5.1/Applications/[left out, personal info]/Documents/Videoclip.mp4
Any ideas? Any help would be greatly appreciated! thank you
EDIT: Location of video clip:
NSString *outputPath = [[NSString alloc] initWithFormat:#"%#/%#", [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0], #"Videoclip.mp4"];
NSURL *outputURL = [[NSURL alloc] initFileURLWithPath:outputPath];
NSLog(#"Completed recording, file is stored at: %#", outputURL);
I have a Data plist (conveniently named Data.plist) that is updated on launch of the app:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Determile cache file path
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *filePath = [NSString stringWithFormat:#"%#/%#", [paths objectAtIndex:0],#"Data.plist"];
NSString *dataURLString = #"http://link/to/Data.plist";
NSURL *dataURL = [[NSURL alloc] initWithString:dataURLString];
NSData *plistData = [NSData dataWithContentsOfURL:dataURL];
[plistData writeToFile:filePath atomically:YES];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSLog(#"The bundle is %#", filePath);
self.data = dict;
// Configure and show the window
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
return YES;
}
I'd like to be able to have some way of checking the saved plist against the server plist - I've seen some implementations that use external libraries but there has to be something in the original iOS SDK. Any ideas? I've read whatever code I do end up using needs to be implemented in viewWillAppear but I'm not sure what that code is exactly.
Two things... first, dataWithContentsOfURL: and generally any of Apple's (temptingly convenient) <anything>WithContentsOfURL: methods are extremely unsafe in the real world. It's blocking which means that no other code will execute until your request succeeds or fails. That means that if the server isn't available or your device doesn't have internet or for some other reason cannot retrieve your data, your phone will sit there until either the iOS watchdog process kills your app for freezing for too long, or it just fails. Then the rest of your app that is expecting data will freak out because suddenly you have no data when your code assumes you should. This is one of many problems with synchronous requests.
I won't go into how to implement asynchronous requests, but head over to Apple's documentation or you can use a wrapper framework like http://allseeing-i.com/ASIHTTPRequest/ that does it for you. Also have a look at http://www.cocoabyss.com/foundation/nsurlconnection-synchronous-asynchronous/
To answer your actual question, you could have a tiny text file on your server with a version number or time stamp and download that along with your plist. on subsequent launches, you can pull down the time stamp/version number and compare it against the one you've got stored, and if the version on the server is more recent, then you pull it and save the new time stamp/version number.