The whole ios video sending/streaming paradigm seems to be a bit vague and uncovered. The task I am trying to perform is similar to that of how videos are played for clients when watching a YouTube videos.
What I am trying to essentially do is quite simple:
I have a pre-made encoded .mov file on my server.
I want to send
that file through a UDP socket to a client.
Here is where I am getting lost:
How do I break down the .mov file to correctly send it over the
socket? Do I need to break down into frames? If so, how?
Right now I am creating an NSData object from the file and breaking it into chunks which I send over the socket like so:
NSURL *fileURL = [[NSBundle mainBundle]
URLForResource:#"test1" withExtension:#"mov"];
NSData *newData = [NSData dataWithContentsOfURL:fileURL];
int index = 0;
int totalLen = (int)[newData length];
while (index < totalLen) {
int space = (totalLen - index > 9216) ? 9216 : totalLen - index;
NSData *chunk = [newData subdataWithRange:NSMakeRange(index, space)];
[udpSocket sendData:chunk toAddress:clientAddress withTimeout:-1 tag:2];
index += 9216;
}
On the client-side, how do I receive and play these packets?
At the moment I'm combining all of the incoming data and concatenating it into one big NSData file which I'd like to play once all of the data is received (which intuitively seems to me like the wrong way to approach this):
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data
fromAddress:(NSData *)address withFilterContext:(id)filterContext{
/* Append nsdata packets from socket until all the file has been received */
[videoData appendData:data];
if([data length] < 9216){
/* Marks EOF. Start playing the video */
[self playVideoData];
}
Then playing the video...
-(void)playVideoData{
NSURL* fileURL = [[NSURL alloc] init];
[videoData writeToURL:fileURL atomically:YES];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:fileURL options:nil];
NSString *tracksKey = #"tracks";
/** Normal AVURLAsset video playing here... */
}
Related
Disclaimer New to AVAudioRecorder
What I'm doing I'm working on an app that uses the iPhone microphone to record sound. After the sound is recorded, I need to convert the sound (should be AVAsset, right?) into NSData to send to our backend.
What's the issue The issue is I am not sure how to "get" the audio that is supposed to be recorded with the AVAudioRecorder. AVAudioRecorder has a delegate method called - (void)audioRecorderDidFinishRecording:(AVAudioRecorder *) aRecorder successfully:(BOOL)flag. I would have expected the actually AVAsset that contains the audio to be passed from this delegate method, but it does not. What it does give me is the aRecorder object that has a .url property on it. When I NSLog the url from the passed aRecorder, it shows up. In fact I can NSLog the length of the file in the code below:
- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *) aRecorder successfully:(BOOL)flag
{
DLog (#"audioRecorderDidFinishRecording:successfully: %#",aRecorder);
AVURLAsset* audioAsset = [AVURLAsset URLAssetWithURL:aRecorder.url options:nil];
CMTime audioDuration = audioAsset.duration;
float audioDurationSeconds = CMTimeGetSeconds(audioDuration);
NSLog(#"asset length = %f", audioDurationSeconds); //Logs 7.051 seconds, so I know it's "there".
self.audioURL = aRecorder.url;
}
Problem When I pass self.audioURL to the next viewController's self.mediaURL and try to grab the file from the AssetLibrary (similarly to how I did before), the asset is not returned from the AssetLibrary (even though when I po self.mediaURL it indeed logs the correct url:
if (self.mediaURL) {
ALAssetsLibrary *assetLibrary = [[ALAssetsLibrary alloc] init];
[assetLibrary assetForURL:self.mediaURL resultBlock:^(ALAsset *asset) {
if (asset) {
// This block does NOT get called...
ALAssetRepresentation *rep = [asset defaultRepresentation];
Byte *buffer = (Byte*)malloc((long)rep.size);
NSUInteger buffered =[rep getBytes:buffer fromOffset:0.0 length:(long)rep.size error:nil];
NSMutableData *body = [[NSMutableData alloc] init];
body = [NSMutableData dataWithBytesNoCopy:buffer length:buffered freeWhenDone:YES];
[dataToSendToServer setObject:body forKey:#"audioData"];
}
} failureBlock:^(NSError *error) {
NSLog(#"FAILED TO ACCESS AUDIO FROM URL: %#!", self.mediaURL);
}];
}
else {
NSLog(#"NO AUDIO DATA!");
}
}
Because I am new to AVAudioRecorder, perhaps I am just not designing this flow correctly. Could anyone help me out in getting the actual audio data.
Thanks!
AVAudioRecorder records to a file, not to the Asset Library.
So you can simply read the data from that file.
So I am using Parse (which is pretty sweet) and I'm in the process of downloading files (short video files - no more then 1mb) from the parse server to my application to play. Now the way it works is (via documentation)..
PFFile* videoFile = [[tempArray objectAtIndex:i] objectForKey:#"track"];
[videoFile getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error) {
NSString* dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSURL* videoURL = [NSURL URLWithString:dataString];
// now do something with this videoURL (i.e. play it!)
[data writeToFile:#"trackFile" atomically:YES];
NSURL *filePath = [NSURL fileURLWithPath:#"trackFile"];
NSLog(#"File Path: %#",filePath);
AVAsset* asset = [AVAsset assetWithURL:filePath];
AVPlayerItem* playerItem = [[AVPlayerItem alloc] initWithAsset:asset];
}
}
On download completion you are suppossed to create a string from the data and then a url from the string. Only problem is - the dataString always returns NULL/nil. I have confirmed that the data property is not empty and does in fact hold the video data. Why is this happening? Any help would be greatly appreciated. Thanks in advance!
I have confirmed that the data property is not empty and does in fact hold the video data.
Video data is not a UTF-8 string. It's definitely not a UTF-8 string representation of an URL. So when you say "interpret this video data as UTF-8," Cocoa rightly responds that it is not UTF-8 (because it's video data).
The simplest solution is to write this to disk, and then play the file.
Couple things that need to be determined first:
does the NSData object actually have any data, i.e., data.length > 0?
does the NSData object hold video data OR the url of the video data (not exactly clear)?
Making the assumption that the NSData object is holding the url and it's length is greater than 0, then you might want to try:
NSString* dataString = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
I'm trying to modify a behavior of a webpage within my iOS app and make the in-page media player play a file from the local caches folder instead of fetching it from a web server.
Below is my code that replaces the http:// video path with a local file path. The code does not work, giving me "Resource Temporary not available. Please try again" error message popup. Is it possible to have a web-based media player play file from a local disk using file URL?
I tried substituting these for the instanceURL, but they don't seem to work.
[fileURL path]
[fileURL absolutePath]
I'm intercepting the request for the file and am parsing it to find out that the page is asking for a video file:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
// An NSURLConnection delegate callback. We pass this on to the client.
{
NSDictionary* decisionDictionary = [[RequestListener sharedInstance] shouldContinue:connection processRequestData:data];
BOOL shouldContinue = [decisionDictionary[#"shouldContinue"] boolValue];
if(shouldContinue == NO)
{
return;
}else
{
NSData* d = data;
//substitute fake data
if(decisionDictionary[#"data"])
{
d = decisionDictionary[#"data"];
}
[[self client] URLProtocol:self didLoadData:d];
}
}
Within my shouldContinue method, I check if the video is present locally and modify the response data to create a path to a local video.
NSString* path = [VideoDownloader localVideoPathForVideoID:videoID];
NSURL* fileURL = [NSURL fileURLWithPath:path];
DLog(#"url:%#",[fileURL absoluteString]);
NSString* replacement = [NSString stringWithFormat:#"\"instanceUrl\":\"%#\"",[[fileURL absoluteURL] absoluteString]];
DLog(#"replacement:%#",replacement);
NSString* forgedResponse = [parts componentsJoinedByString:#","];
NSData* forgedData = [forgedResponse dataUsingEncoding:NSUTF8StringEncoding];
return #{#"shouldContinue":#(YES),#"data":forgedData};
Have a look at NSURLProtocol. You can intercept http requests before they are sent to a host to decide what to do about it: Continue to server, redirect to local cache.
There's a decent tutorial by our beloved Ray Wenderlich.
Apple has a programming guide as well.
I am writing an app that will play MIDI files from parsed URL's. Currently, my code downloads the file then plays it using MusicPlayer and MusicSequence.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
NSData *midData = [[NSData alloc] initWithContentsOfURL:playURL];
NSString *resourceDocPath = [[NSString alloc] initWithString:[[[[NSBundle mainBundle] resourcePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:#"Documents"]];
NSString *filePath = [resourceDocPath stringByAppendingPathComponent:#"part.mid"];
[midData writeToFile:filePath atomically:YES];
NSLog(#"Downloaded");
dispatch_async(dispatch_get_main_queue(), ^{
NSURL *url = [NSURL fileURLWithPath:filePath];
MusicSequence s;
NewMusicSequence(&s);
MusicSequenceFileLoad(s, (__bridge CFURLRef)(url), 0, 0);
NewMusicPlayer(&midPlayer);
MusicPlayerSetSequence(midPlayer, s);
MusicPlayerPreroll(midPlayer);
MusicPlayerStart(midPlayer);
MusicTrack t;
MusicTimeStamp len;
UInt32 sz = sizeof(MusicTimeStamp);
MusicSequenceGetIndTrack(s, 1, &t);
MusicTrackGetProperty(t, kSequenceTrackProperty_TrackLength, &len, &sz);
midPlaying = YES;
});
});
What I'm looking for is a notification when the file reached its end, similar to something like AVPlayer:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(itemDidFinishPlaying:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];
Is this possible? I've been looking through Apple's documentation and cannot find anything.
Documentation: MusicSequence Reference, MusicPlayer Reference
I don't recall ever seeing any built in way to do that but you can easily implement it through polling playback status on your own. CADisplayLink is a built in timer you might want to use if you are updating a progress bar or doing other screen drawing (or are just lazy like me).
Being confronted to the same problem and having searched around, I don't think there's a way of doing that.
I am receiving a stream of encoded audio over the network, but there is a bunch of data mixed in so I need to receive the packets, strip out the audio then play the audio.
AVAudioPlayer, which may or may not be the best tool for this but it's the path I'm currently chasing, wants data from NSData or NSURL. NSData won't work because it is a stream of data and I want it to start as soon as it arrives and continue playing. My thought was:
NSPipe *pipe = [[NSPipe alloc] init];
NSFileHandle *writeHandle = [pipe fileHandleForWriting];
NSFileHandle *readHandle = [pipe fileHandleForReading];
// in network reception thread...
NSData *audioData = [packet getAudioData];
[writeHandle writeData:audioData];
// in audio thread...
NSURL *url = [[NSURL alloc] init];
[url setResourceValue:NSURLFileResourceTypeNamedPipe
forKey:NSURLFileResourceTypeKey
error:&error];
// Connect the readHandle
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc]
initWithContentsOfURL:url
error:&error];
However, I don't know how to pass the `readHandle` into the URL. How do I create an NSURL from an existing NSFileHandle? Is there some better approach for this entirely? Is there a way to write data into something that can become an NSURL?
The only real requirement is that I can play the audio as near real time as possible. I don't want to queue up data for even a tenth of a second before it gets played.
It is not possible to retrieve an NSFileHandle's URL because not all handles have a corresponding URL. Your example would appear to be one such example.