Playing two videos in sequence withou any gap or pause between them in iOS 5 - objective-c

In my app, I should play firstvideo at once, and then play second video, which will on repeat. So, at first I used MPMoviePlayerController, but there already was a black screen between two videos. So, I read somewhere, that in my case I should use AVFoundation. That's whe I use AVQueuePLayer with two AVPLayerItem, which I create with my NSURL. But issue is, that after first video finished, there is a long pause. Nearly for 0.5 sec, before second vide o starts. How remove it? What way to use? So, I really appreciate any help! I need it very much, as soon as possible. Thanks!
Here all my methods which I use:
- (void) configureAVPlayer {
self.queuePlayer.actionAtItemEnd = AVPlayerActionAtItemEndPause;
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:self.queuePlayer];
self.isFirstVideoFinished = NO;
[self playVideos];
}
- (void) playVideos {
[self setPlayerItemsForQueue];
[self.videoHolder setPlayer:self.queuePlayer];
}
- (void) setPlayerItemsForQueue {
NSURL *fileURL = [[NSBundle mainBundle]
URLForResource:[self getStringForMode:self.currentMode] withExtension:#"mp4"];
NSMutableString *secondFile = [self getStringForMode:self.currentMode];
[secondFile appendString:#"Second"];
NSURL *secondFileUrl = [[NSBundle mainBundle] URLForResource:secondFile withExtension:#"mp4"];
AVAsset *firstAsset = [AVAsset assetWithURL:fileURL];
AVPlayerItem *firstVideoItem = [AVPlayerItem playerItemWithAsset:firstAsset];//[AVPlayerItem playerItemWithURL:fileURL];
AVAsset *secondAsset = [AVAsset assetWithURL:secondFileUrl];
AVPlayerItem *secondVideoItem = [AVPlayerItem playerItemWithAsset:secondAsset];//[AVPlayerItem playerItemWithURL:secondFileUrl];
self.queuePlayer = [AVQueuePlayer queuePlayerWithItems: [NSArray arrayWithObjects:firstVideoItem, secondVideoItem,nil]];
for (AVPlayerItem *playerItem in self.queuePlayer.items) {
[playerItem addObserver: self forKeyPath: #"status" options:0 context:NULL];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
dispatch_async(dispatch_get_main_queue(), ^{
if ([(AVPlayerItem *)object status] == AVPlayerItemStatusReadyToPlay) {
[self.queuePlayer play];
}
});
}
- (void) playerItemDidReachEnd:(NSNotification*) notification {
NSMutableString *secondFile = [self getStringForMode:self.currentMode];
[secondFile appendString:#"Second"];
NSURL *secondFileUrl = [[NSBundle mainBundle] URLForResource:secondFile withExtension:#"mp4"];
AVPlayerItem *playerItem = [[ AVPlayerItem alloc ] initWithURL:secondFileUrl];
if ([self.queuePlayer canInsertItem:playerItem afterItem:[notification object]]) {
[self.queuePlayer insertItem: playerItem afterItem: nil ];
}
}

Yes, multiple AVPlayer objects are your best option.
I solved this problem with a main timer function which monitored the current playback time of firstVideoItem, and when it was 0.01 seconds from the end, I would flip the players on the alpha channel, so secondVideoItem was visible, then play secondVideoItem.
I also used [secondVideoItem seekToTime:CMTimeMakeWithSeconds(1,1)] before issuing the play command to make sure the second video was initialised and ready to play. This helped a lot.

Related

why MPMoviePlayerViewController having different behavior with the same URL?

I implemented MPMovieViewController in my app but it has a problem i.e.when i play video in player then first time it is failed to play but next time it will play successfully with the same URL. I am not able to understand this different behavior with the same URL. I copied code here which i used in my app.
NSURL *fileURL = [NSURL URLWithString:#"http://nordenmovil.com/urrea/InstalaciondelavaboURREAbaja.mp4"];
MPMoviePlayerViewController * controller = [[MPMoviePlayerViewController alloc]initWithContentURL:fileURL];
controller.moviePlayer.movieSourceType= MPMovieSourceTypeFile;
[controller.moviePlayer setControlStyle:MPMovieControlStyleDefault];
[controller.moviePlayer prepareToPlay];
[controller.moviePlayer play];
[self presentMoviePlayerViewControllerAnimated:controller];
I suggest to handle the error for see what happens with your URL video (this is a tip that I founded long time ago in this answer)
Add this for capture the end of play:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleMPMoviePlayerPlaybackDidFinish:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:nil];
and this for see what happens:
- (void)handleMPMoviePlayerPlaybackDidFinish:(NSNotification *)notification
{
NSDictionary *notificationUserInfo = [notification userInfo];
NSNumber *resultValue = [notificationUserInfo objectForKey:MPMoviePlayerPlaybackDidFinishReasonUserInfoKey];
MPMovieFinishReason reason = [resultValue intValue];
if (reason == MPMovieFinishReasonPlaybackError)
{
NSError *mediaPlayerError = [notificationUserInfo objectForKey:#"error"];
if (mediaPlayerError)
{
NSLog(#"playback failed with error description: %#", [mediaPlayerError localizedDescription]);
}
else
{
NSLog(#"playback failed without any given reason");
}
}
}
Don't forget to remove the notification handler:
[[NSNotificationCenter defaultCenter] removeObserver:self
name:MPMoviePlayerPlaybackDidFinishNotification
object:nil];
With this workaround you should know the error with your stream, I hope this helps!

AVQueuePlayer (or AVPlayer) with a Soundcloud stream item block the UI (Main Thread)

Thank to those two posts, I resolve this problem during the creation of the AVPLayerItem
AVQueuePlayer playback without gap and freeze
AVPlayer "freezes" the app at the start of buffering an audio stream
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:[NSURL URLWithString:urlString] options:#{AVURLAssetPreferPreciseDurationAndTimingKey : #(YES)}];
NSArray *keys = #[#"playable", #"tracks",#"duration" ];
[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^()
{
for (NSString *thisKey in keys) {
NSError *error = nil;
AVKeyValueStatus keyStatus = [asset statusOfValueForKey:thisKey error:&error];
if (keyStatus == AVKeyValueStatusFailed) {
return ;
}
}
AVPlayerItem *item = [[AVPlayerItem alloc] initWithAsset:asset];
dispatch_async(dispatch_get_main_queue(), ^ {
[item addObserver:self forKeyPath:#"status" options:NSKeyValueObservingOptionNew context:nil];
[player insertItem:item afterItem:nil];
});
}];
});
But, with a stream from Soundcloud (https://api.soundcloud.com/tracks/146924238/stream?client_id=76395c48f97de903ff44861e230116bd), after the item's status is ready to play (AVPlayerStatusReadyToPlay), the AVQueueplayer keep doing something in the main thread "freezing" the UI (Track keep playing in background thread).
I noticed this problem is more important when the network is low, Then I made the conclusion that this freeze exist when the item is buffering the stream.
Someone have the solution or a trick?

How to Reload Text Inside UITableCell using AFNetworking

I'm fetching the value of my UILabel from Rotten Tomatoes API and I want my text inside my custom cell to update whenever the value has been fetched already. I tried doing that by reloading my UITableView but it goes on a loop.
Here's my code:
if (ratingTomatoLabel.text == nil)
{
NSLog(#" Nil");
NSString *stringWithNoSpaces = [movieTitle.text stringByReplacingOccurrencesOfString:#" " withString:#"%20"];
NSString *rottenTomatoRatingString = [NSString stringWithFormat:#"%#%#%#", #"http://api.rottentomatoes.com/api/public/v1.0/movies.json?apikey=6844abgw34rfjukyyvzbzggz&q=", stringWithNoSpaces, #"&page_limit=1"];
NSLog(#" Rotten URL: %#", rottenTomatoRatingString);
NSURL *rottenUrl = [[NSURL alloc] initWithString:rottenTomatoRatingString];
NSURLRequest *rottenRequest = [[NSURLRequest alloc] initWithURL:rottenUrl];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:rottenRequest success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSLog(#" 3");
self.testDict = [JSON objectForKey:#"movies"];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
[operation start];
NSLog(#" %#", ratingTomatoLabel.text);
ratingTomatoLabel.text = #"...";
}
else if ([ratingTomatoLabel.text isEqualToString:#""])
{
NSLog(#" isEqualToString");
// Do nothing
}
else
{
NSLog(#" else");
}
return tableCell;
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(#" fetched!");
if ([ratingTomatoLabel.text isEqualToString:#"..."])
{
NSLog(#" 2nd nil");
NSDictionary *dict = [self.testDict valueForKey:#"ratings"];
self.criticsScoreString = [NSString stringWithFormat:#"%#", [dict valueForKey:#"critics_score"]];
self.criticsScoreString = [self.criticsScoreString stringByReplacingOccurrencesOfString:#" " withString:#""];
self.criticsScoreString = [self.criticsScoreString stringByReplacingOccurrencesOfString:#"(" withString:#""];
self.criticsScoreString = [self.criticsScoreString stringByReplacingOccurrencesOfString:#")" withString:#""];
self.criticsScoreString = [NSString stringWithFormat:#"%#%#", self.criticsScoreString, #"%"];
ratingTomatoLabel.text = self.criticsScoreString;
NSLog(#" %#", ratingTomatoLabel.text);
}
else
{
NSLog(#" 2nd not nil");
NSLog(#" %#", ratingTomatoLabel.text);
// Do nothing
}
}
If I added the code [self.myTableView reloadData]; after setting the value in my text, it goes on a loop. What's the best way to do this? Thanks!
UPDATE: Included updated code and another method. Also, my ratingTomatoLabel always goes nil when it's not displayed.
You should use KVO (Key-value observing) to detect when there are changes to your dictionary or array - in this case it seems self.testDict.
When you receive this notification, use UITableView's -visibleCells method to update only the visible cells - the others will obviously take on their new value when their cell is recycled using -tableView:cellForRowAtIndexPath:.
What's required for KVO: Registering/deregistering when views load/change and setting the variable you wish to observe...
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if([keyPath isEqual:#"testDict"])
{
NSLog(#"KVO change: %#", change);
NSArray *visibleCells = [self.myTableView visibleCells];
for(UITableViewCell *cell in visibleCells)
{
if(/*cell matches the one I'm targeting OR just update regardless*/)
// assign value to UILabel
}
}
}
// Call this from -viewDidLoad
- (void)registerAsObserver
{
[self addObserver:self forKeyPath:#"testDict" options:NSKeyValueObservingOptionNew context:NULL];
}
// Call this from -viewWillDisappear and -viewDidUnload (may not be necessary for -viewDidUnload if already implemented in -viewWillDisappear)
- (void)unregisterAsObserver
{
[self removeObserver:self forKeyPath:#"testDict"];
}
Lastly, to update the cell(s) that have changed, call the -reloadRowsAtIndexPaths:withRowAnimation: method to have the underlying system refresh the cell.
Jaytrix
With the AFNetworking calls happening asynchronously, consider using some sort of notification to tell the main thread (UI) when work is complete or an error occurs.
Frank

Crashing in observeValueForKeyPath which attached to NSOperationQueue operationsCount

I have UITextView logView (tried both atomic and nonatomic) and NSOperationQueue uploadQueue properties in my UIView subclass.
I've added KVO on my NSOperationQueue property's operationsCount like this:
[[self uploadQueue] addObserver:self
forKeyPath:#"operationCount"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:NULL];
Observer function looks like this:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if ([object isKindOfClass: [NSOperationQueue class]]) {
NSLog(#"Keypath is: %# change dictionary is: %#", keyPath, change);
NSInteger testnew = [[change objectForKey: #"new"] integerValue];
NSInteger testold = [[change objectForKey: #"old"] integerValue];
if (testnew > testold) {
[[self logView] setText: [NSString stringWithFormat: #"Uploading %d files", testnew]];
objc_setAssociatedObject([self logView], #"max_value_of_uploads", [change objectForKey: #"new"], OBJC_ASSOCIATION_COPY);
} else {
NSInteger value = [objc_getAssociatedObject([self logView], #"max_value_of_uploads") integerValue];
[[self logView] setText: [NSString stringWithFormat: #"Uploaded %d of %d files", testnew, value]];
}
}
}
uploadQueue filling works like that:
...
NSDictionary *iter;
NSEnumerator *enumerator = [objects objectEnumerator];
while (iter = [enumerator nextObject]) {
Uploader *op = [[Uploader alloc] initWithFileName: [iter objectForKey: #"fileName"]
FileID: [[iter objectForKey: #"fileID"] integerValue]
AndSessionID: [self sess]];
//Uploader *op = [[Uploader alloc] init];
[[self uploadQueue] addOperation: op];
...
Without if-else block, everything works just fine: I've got NSLog messages about number of operations in queue, numbers are ok and so on.
But with that if-else block, I get crash, which looks like this:
bool _WebTryThreadLock(bool), 0x10617fb0: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...
1 WebThreadLock
2 -[UITextView setText:]
3 -[SincViewController observeValueForKeyPath:ofObject:change:context:]
4 NSKeyValueNotifyObserver
5 NSKeyValueDidChange
6 -[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:]
7 -[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:usingBlock:]
8 ____NSOQDelayedFinishOperations_block_invoke_0
9 -[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:]
10 -[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:usingBlock:]
11 __NSOQDelayedFinishOperations
12 _dispatch_after_timer_callback
13 _dispatch_source_invoke
14 _dispatch_queue_invoke
15 _dispatch_worker_thread2
16 _pthread_wqthread
17 start_wqthread
It crashes in else block.
Moreover, I don't see any changes to my logView.
Thanks for future help and responses.
UPDATE: performSelectorOnMainThread when setting text saved me.
When updating your UI, make sure that you're in the main thread. A possible solution follows:
dispatch_async(dispatch_get_main_queue(), ^{
if (testnew > testold) {
[[self logView] setText: [NSString stringWithFormat: #"Uploading %d files", testnew]];
objc_setAssociatedObject([self logView], #"max_value_of_uploads", [change objectForKey: #"new"], OBJC_ASSOCIATION_COPY);
} else {
NSInteger value = [objc_getAssociatedObject([self logView], #"max_value_of_uploads") integerValue];
[[self logView] setText: [NSString stringWithFormat: #"Uploaded %d of %d files", testnew, value]];
}
});
Note that using dispatch_async or dispatch_sync depends on your desired implementation, but in the latter case, you risk deadlocking unless you check what thread you're in.
See also: NSOperation, observer and thread error
where someone suggests moving your UI code to a separate method:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
[self performSelectorOnMainThread:#selector(dataLoadingFinished:) withObject:nil waitUntilDone:YES];
}
.. which is a fine alternate route.
I would suggest you use the delegate pattern here. You seem to be mixing very advanced techniques with some loose code, which can lead to problems very quickly. I don't think you need the associative object for example.
Your error occurs because you are accessing UI from a background thread by the way. You should wrap you UI calls (setText:) inside performSelectorOnMainThread:withObject:waitUntilDone: or a block.

IOS can I use AVAudioPlayer on the appDelegate?

I have a TabBarController with two tabs and I want to play music on both tabs. Right now I have my code on the main appDelegate
NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle]
pathForResource:#"My Song"
ofType:#"m4a"]]; // My Song.m4a
NSError *error;
self.audioPlayer = [[AVAudioPlayer alloc]
initWithContentsOfURL:url
error:&error];
if (error)
{
NSLog(#"Error in audioPlayer: %#",
[error localizedDescription]);
} else {
//audioPlayer.delegate = self;
[audioPlayer prepareToPlay];
}
but I'm getting the error Program received signal: "SIGABRT" on UIApplicationMain
Is there a better way to accomplish what I'm trying to do? If this is how I should do it, where do I start checking for problems?
yes you can use AVAudioPlayer in App Delegate.
What you need to do is:-
In appDelegate.h file do:-
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>
AVAudioPlayer *_backgroundMusicPlayer;
BOOL _backgroundMusicPlaying;
BOOL _backgroundMusicInterrupted;
UInt32 _otherMusicIsPlaying;
Make backgroundMusicPlayer property and sythesize it.
In appDelegate.m file do:-
Add these lines in did FinishLaunching method
NSError *setCategoryError = nil;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:&setCategoryError];
// Create audio player with background music
NSString *backgroundMusicPath = [[NSBundle mainBundle] pathForResource:#"SplashScreen" ofType:#"wav"];
NSURL *backgroundMusicURL = [NSURL fileURLWithPath:backgroundMusicPath];
NSError *error;
_backgroundMusicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:backgroundMusicURL error:&error];
[_backgroundMusicPlayer setDelegate:self]; // We need this so we can restart after interruptions
[_backgroundMusicPlayer setNumberOfLoops:-1]; // Negative number means loop forever
Now implement delegate methods
#pragma mark -
#pragma mark AVAudioPlayer delegate methods
- (void) audioPlayerBeginInterruption: (AVAudioPlayer *) player {
_backgroundMusicInterrupted = YES;
_backgroundMusicPlaying = NO;
}
- (void) audioPlayerEndInterruption: (AVAudioPlayer *) player {
if (_backgroundMusicInterrupted) {
[self tryPlayMusic];
_backgroundMusicInterrupted = NO;
}
}
- (void)tryPlayMusic {
// Check to see if iPod music is already playing
UInt32 propertySize = sizeof(_otherMusicIsPlaying);
AudioSessionGetProperty(kAudioSessionProperty_OtherAudioIsPlaying, &propertySize, &_otherMusicIsPlaying);
// Play the music if no other music is playing and we aren't playing already
if (_otherMusicIsPlaying != 1 && !_backgroundMusicPlaying) {
[_backgroundMusicPlayer prepareToPlay];
if (soundsEnabled==YES) {
[_backgroundMusicPlayer play];
_backgroundMusicPlaying = YES;
}
}
}