I am reading video from local using AVPlayer. When I scrub through the video, sometimes I am getting pixelated video for few second or green screen appears for a while.
Following is my code for scrubbing:
- (void)playerControlView:(PlayerControlView *)playerControlView beganScrubbingAtPercent:(float)timePercent
{
if (_avPlayer.rate > 0) {
_resumePlayAfterScrub = YES;
} else {
_resumePlayAfterScrub = NO;
}
}
- (void)playerControlView:(PlayerControlView *)playerControlView scrubbedToPercent:(float)timePercent
{
if (_avPlayer.rate > 0) {
[_avPlayer pause];
[_loadingView startAnimating];
}
float durationSeconds = CMTimeGetSeconds(_avPlayerItem.duration);
CMTime cmTime = CMTimeMakeWithSeconds(durationSeconds * timePercent, NSEC_PER_SEC);
[_avPlayer seekToTime:cmTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
[_hideControlsTimer invalidate];
}
- (void)playerControlView:(PlayerControlView *)playerControlView finishedScrubbingAtPercent:(float)timePercent
{
if (_resumePlayAfterScrub) {
[_avPlayer play];
}
[self resetCurrentEventWithCopy:YES];
}
what can I optimize on above code?
It could be a case wherein the gap between consecutive IDR frames is high so when you seek, it takes a while to hit the closest IDR frame and clear picture buffer. Till the player hits an IDR frame, it will not be able to decode the stream properly which will result in you seeing green screens and pixelations.
Related
I currently have a screen that loads a video through a method called - loadCurrentVideo and this method usually takes 2-3 seconds to complete while on wifi but 10-20 seconds to complete while on data. I have a button built into the player that exits out of the player when it is tapped, but the button only works after the video is loaded. I want to be able to add a button that exits out of the player whenever I tap it at any point during the loading and playing stages.
While the video is loading, I can't use any of the buttons on the player since i'm assuming the video is still loading and executing the method described above. How can I build a button that is capable of always being available to respond to a tap and interrupts a method while it is executing to return to the previous screen that the app was on?
Below is the method:
- (void) loadCurrentVideo {
__block UIActivityIndicatorView *pw = _pleaseWait;
__block KolorEyes *ke = _koloreyes;
__block UISlider *slider = _timeSlider;
_pleaseWait.hidden = NO;
[_pleaseWait startAnimating];
dispatch_async(dispatch_get_main_queue(), ^{
NSURL *url1 = [NSURL URLWithString:url];
[_koloreyes setDisplayDevice:kKE_DISPLAY_HMD forView:_viewRectHandle];
[_koloreyes setCameraControlMode:kKE_CAMERA_TOUCH forView:_viewRectHandle];
err = [_koloreyes setAssetURL:url1
withCompletionHandler:^{
NSLog(#"Video %#", url1.path);
[pw stopAnimating];
pw.hidden = YES;
CMTime duration = [ke getMediaDuration];
if (CMTIME_IS_INDEFINITE(duration)) {
slider.hidden = YES;
}
else {
[slider setMaximumValue:CMTimeGetSeconds(duration)];
}
[ke play];
[self postView];
Log("Kolor Eyes set asset URL completion handler");
}];
if (err != kKE_ERR_NONE) {
[pw stopAnimating];
pw.hidden = YES;
Log("Kolor Eyes setAssetURL failed with error %d", err);
}
});
}
I'm trying to set alpha of a SKNode every time it is clicked either to 0 or 1. My code currently turns it off but won't turn it back on. Any idea why?
- (void)handleTouchedPoint:(CGPoint)touchedPoint {
touchedNode = [self nodeAtPoint:touchedPoint];
// Detects which node was touched by utilizing names.
if ([touchedNode.name isEqualToString:#"play"]) {
isOnPlay = true;
NSLog(#"Touched play");
}
if ([touchedNode.name isEqualToString:#"light1"]) {
//NSLog(#"%.2f", touchedNode.alpha);
if(touchedNode.alpha != 0.0)
{
NSLog(#"Off");
touchedNode.alpha = 0.0;
//[touchedNode setAlpha:0.0];
}
else{
NSLog(#"On");
touchedNode.alpha = 1.0;
//[touchedNode setAlpha:1.0];
}
NSLog(#"Touched light");
}
}
You might be running into the famous float rounding issue. Use debug and check the values. The alpha might not be exactly zero.
- (void)viewDidLoad {
[super viewDidLoad];
self.slider.maximumValue = 30;
}
- (void)slide:(UISlider *)slider {
NSLog(#"....progress slider value:%d", (int)slider.value);
CMTime showingTime = CMTimeMakeWithSeconds((int)slider.value, 1);
[self.player seekToTime:showingTime];
}
Everytime I slide the UISlider, The console log the right number as I slided. like 6 seconds. But the slider would jump back to 0; If I slide it to value bigger than 10, like 14, It would slide back to 10.
Can anybody help me ? Thanks
Try this.
You gotta init a CMTime object based on UISlider's value at first. Then you call AVPlayer object's seek to time method. Pass the CMTime object as parameter.
CMTime showingTime = CMTimeMake(slider.value *1000, 1000);
[self.player seekToTime:showingTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
I need to organize rendering loop in my app by a specific way (there is a reasons).
Let's say I have
Sprite *sprite = [[Sprite alloc] initWithContentsOfFile:#"sprite.png"]; // some sprite
while (true) {
[Graphics update];
sprite.x = (sprite.x + 1) % 1024 // moving sprite by one pixel each frame
[Input update];
[self update];
}
There Graphics.update should render one frame and delay execution (not rendering) until next frame
#interface Graphics () {
static BOOL _frozen;
static int _count;
static int _frameCount;
static float _frameRate;
static double _lastFrameTimestamp;
}
#end
#implementation Graphics
+ (void)initialize {
_frozen = NO
_count = 0
_frameCount = 0
_frameRate = 30.0
_lastFrameTimestamp = CACurrentMediaTime()
}
+ (void)freeze {
_frozen = YES;
}
+ (void)update {
if _frozen
return
end
Video.nextFrame // some OpenGL ES magic to render next frame
_count++
now = CACurrentMediaTime()
waitTill = _lastFrameTimestamp + _count * (1.0 / _frameRate)
if now <= waitTill
sleep(waitTill - now)
else
_count = 0
_lastFrameTimestamp = CACurrentMediaTime()
end
_frameCount++
}
#end
Somehow it works and sprite is moving. But when I go to home applicationWillResignActive isn't called and when I go back to app there is black screen and after some time app crashes.
Here is the thing I try to port: https://bitbucket.org/lukas/openrgss/src/7d9228cc281207fe00a99f63b507198ea2596ead/src/graphics.c (Graphics_update function)
You can try using Core Animation DisplayLink instead of a while loop. That's how it's usually done in the graphics frameworks. The currentRunLoop calls your update method every 1/60 seconds.
You should remove the sleep call in your update if using NSRunLoop.
CADisplayLink *displayLink;
// Set your update method
displayLink = [CADisplayLink displayLinkWithTarget:[Graphics class]
selector:#selector(update)];
// Set fps to device refresh rate (60)
[displayLink setFrameInterval:1.0f];
// Add your display link to current run loop
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
// Stop updating
[displayLink invalidate];
The last line stops the execution, so do not call that until you're done with your loop.
If you are using Sparrow, this is how you should be handling it:
SPSprite *sprite = [[SPSprite alloc] initWithContentsOfFile:#"sprite.png"]; // some sprite
[self addEventListener:#selector(enterFrame:) atObject:self forType:SP_EVENT_TYPE_ENTER_FRAME];
-(void)enterFrame:(SPEnterFrameEvent*)e {
[Graphics update];
sprite.x += 1; // moving sprite by one pixel each frame
[Input update];
[self update];
}
Sparrow manages the game loop for you using an SP_ENTER_FRAME_EVENT. It will be called every time it's time to render again. Roughly 30 times per second (though it can be configured).
I want to implement startup loader in my app. It should be like this: after startup splash screen, user will watch simple animataion and in meanwhile app preload sound effects, background music, sprite images, spritesheets and so on. Current implementation:
- (id)init {
if((self = [super init])) {
// Some other setup ...
CGRect rect;
rect = waveSprite.textureRect;
waveInitialTexRectOrigin = rect.origin;
rect.size.width = 91;
waveSprite.textureRect = rect;
assetFilenames = [[NSArray alloc] initWithObjects:
// images
#"background.png",
// spritesheets
#"sprites.plist",
// fonts
#"main.png",
// sound effects
#"button.wav",
nil];
assetCounter = 0;
[self loadAsset];
}
return self;
}
- (void)update:(ccTime)dt {
CGRect rect;
rect = waveSprite.textureRect;
rect.origin.x += dt*kLoaderWaveSpeed;
while (rect.origin.x > waveInitialTexRectOrigin.x + kLoaderWavePeriod) {
rect.origin.x -= kLoaderWavePeriod;
}
waveSprite.textureRect = rect;
}
#pragma mark Private
- (void)loadAsset {
// CCLOG(#"loadAsset");
NSString *filename = [assetFilenames objectAtIndex:assetCounter];
CCLOG(#"loading %#", filename);
NSString *ext = [filename pathExtension];
if ([ext doesMatchRegStringExp:#"[png|jpg]"]) {
[[CCTextureCache sharedTextureCache] addImage:filename];
} else if ([ext isEqualToString:#"plist"]) {
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:filename];
} else if ([ext doesMatchRegStringExp:#"[caf|wav|mp3]"]) {
[[SimpleAudioEngine sharedEngine] preloadEffect:filename];
}
assetCounter++;
if (assetCounter < [assetFilenames count]) {
[self performSelector:#selector(loadAsset) withObject:self afterDelay:0.1f];
} else {
[self performSelector:#selector(loadingComplete) withObject:self afterDelay:0.2];
}
But the animation is SO abrupt.
UPD I've already tried
[self performSelectorInBackground: withObject:]
but it didn't seem to work (hung on loading first asset). Maybe I should try better in this direction.
UPD2 Smooth = not abrupt, without delays and flicker. fps doesn't matter, 20 fps quite OK
I'm not experienced with CC2D, but here's what I used in my ViewController to show a smooth animated loading indicator:
//Set up and run your loading scene/indicator here
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
//Load your scene
dispatch_async( dispatch_get_main_queue(), ^{
//This fires when the loading is complete
//Show menu and destroy the loader scene/indicator here
});
});
I guess it's worth a try.
You should not be using performSelectorInBackground anymore, it is not deprecated but it might be soon, with GCD now in the picture.
What I would try is run the animation in the main thread since it is the only one that can do UI updates. And spawn one or more threads using GCD to load all your assets.
I would use concurrent dispatch queues to load everything in parallel and simply dismiss the animation when this is done.
You can get complete information about concurrent dispatch queues here:
Concurrency Programming Guide
Hope that helps.
That is because you are doing everything on the same thread. Use a separate thread for the loader.
Hope this helps.
Cheers!
EDIT : Take a look at this.