After adding observer to AVPlayer I suddenly lost the AVPlayerViewController controls.
-(void)viewDidAppear:(BOOL)animated {
AVPlayerItem *playerItem = [[AVPlayerItem alloc]initWithURL:_playbackInfo.streamURL];
// logo to show
CGRect viewRect = CGRectMake(10, 10, 30, 30);
UIView *overlayView = [[UIView alloc] initWithFrame:viewRect];
UIImageView *image = [[UIImageView alloc]initWithFrame:viewRect];
image.image = _playbackInfo.logoType;
[overlayView addSubview:image];
self.player = [[AVPlayer alloc]initWithPlayerItem:playerItem];
[self.contentOverlayView addSubview:overlayView];
self.player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
[self.player addObserver:self
forKeyPath:#"rate"
options:NSKeyValueObservingOptionNew
context:NULL];
[self.player play];
[self.navigationController setNavigationBarHidden:YES animated:NO];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSString *,id> *)change
context:(void *)context {
if ([keyPath isEqualToString:#"rate"]) {
float rate = [change[NSKeyValueChangeNewKey] floatValue];
if (rate == 0.0) {
_sessionStarted = NO;
NSLog(#"Playback stopped");
} else if (rate == 1.0) {
if(!_sessionStarted) {
LTSessionManager *sessionManager = [LTSessionManager sharedInstance];
sessionManager.delegate = self;
[sessionManager startSessionManager:self.playbackInfo];
[sessionManager getSessionStatus];
NSLog(#"Start session");
_sessionStarted = YES;
}
} else if (rate == -1.0) {
// Reverse playback
}
}
}
Every time when I remove - (void)observeValueForKeyPath: the controls are back, but then I won't get observer. Any ideas? Thank you in advance.
It might be too late but hopefully can help others when they came to this issue. I found that AVPlayerViewController has their own observer(s) that will call -(void)observeValueForKeyPath:
The solution is call the super.observeValueForKeyPath: I will show the code in Swift:
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if keyPath == "rate" {
//your code here
} else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
In your code you did not add the self.player to any view. That why it is not showing the player on the view.
Try this sample:
-(void)viewDidAppear:(BOOL)animated {
AVAsset *asset = [AVAsset assetWithURL: [NSURL URLWithString: "Your video URL here"]];
AVPlayerItem *item = [[AVPlayerItem alloc] initWithAsset:asset];
AVPlayer *player = [[AVPlayer alloc] initWithPlayerItem:item];
player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
AVPlayerLayer *layer = [AVPlayerLayer playerLayerWithPlayer:player];
UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0, 320.0f, 200.0f)];
layer.frame = self.view.frame;
[containerView.layer addSublayer:layer];
[self.view addSubview:containerView];
layer.backgroundColor = [UIColor greenColor].CGColor;
[layer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
[player play];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSString *,id> *)change
context:(void *)context {
if ([keyPath isEqualToString:#"rate"]) {
float rate = [change[NSKeyValueChangeNewKey] floatValue];
if (rate == 0.0) {
_sessionStarted = NO;
NSLog(#"Playback stopped");
} else if (rate == 1.0) {
if(!_sessionStarted) {
LTSessionManager *sessionManager = [LTSessionManager sharedInstance];
sessionManager.delegate = self;
[sessionManager startSessionManager:self.playbackInfo];
[sessionManager getSessionStatus];
NSLog(#"Start session");
_sessionStarted = YES;
}
} else if (rate == -1.0) {
// Reverse playback
}
}
}
Related
I have to play the same local video file with the AVPlayer at the same time, and I created 4 AVPlayer instance, and the AVPlayerLayers was added to tha same layer. But the question is that 4 player were not begin at the same time. How to make them begin at the same time? here is my code:
self.players = #[].mutableCopy;
CMAudioClockCreate(kCFAllocatorDefault, &_syncClock);
AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:self.url];
for (NSInteger i = 0; i<playerNum; i++) {
AVPlayer *avPlayer = [AVPlayer playerWithPlayerItem:item.copy];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:avPlayer];
//设置模式
playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
playerLayer.contentsScale = [UIScreen mainScreen].scale;
CGPoint pos = [playerOriginArr[i] CGPointValue];
playerLayer.frame = CGRectMake(pos.x, pos.y, playerSize.width, playerSize.height);
[self.playBackBgView.layer addSublayer:playerLayer];
avPlayer.masterClock = _syncClock;
[avPlayer.currentItem addObserver:self forKeyPath:#"status" options:NSKeyValueObservingOptionNew context:nil];
[avPlayer play];
[self.players addObject:avPlayer];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:#"status"] && [object isKindOfClass:[AVPlayerItem class]]) {
AVPlayerItem *playerItem = (AVPlayerItem *)object;
if (playerItem.status == AVPlayerStatusReadyToPlay) {
for (AVPlayer *player in self.players) {
if (player.currentItem == playerItem) {
[player prerollAtRate:1.0 completionHandler:^(BOOL finished) {
if (finished) {
}
}];
player.automaticallyWaitsToMinimizeStalling = NO;//如果是
NSLog(#"setRate");
[player setRate:1.0 time:kCMTimeInvalid atHostTime:CMClockGetTime(_syncClock)];
}
}
}
}
}
Create just one AVPlayer and create multiple AVPlayerLayers from that.
The layers will be synchronized.
Delay is about 10 seconds.
Snippet of my code. It's *.m file of HomeController:
#interface HomeController ()
#property(nonatomic, strong) AVPlayerViewController *playerViewController;
#implementation HomeController
- (void)viewDidLoad
{
[super viewDidLoad];
...
self.playerViewController = [[AVPlayerViewController alloc] init];
}
- (IBAction)watchDemoToggle:(id)sender {
NSURL *url = [NSURL URLWithString:#"http://blahblahblah.com/demo.mp4"];
AVURLAsset *asset = [AVURLAsset assetWithURL: url];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
if(playerItem)
{
AVPlayer *player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
self.playerViewController.player = player;
[self.playerViewController.player addObserver:self forKeyPath:#"status" options:0 context:nil];
[self.playerViewController setShowsPlaybackControls:NO];
[self presentViewController:self.playerViewController animated:YES completion:^{
[self.playerViewController.player play];
self.playerViewController.showsPlaybackControls = YES;
}];
}
}
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ([keyPath isEqualToString:#"status"]) {
AVPlayerStatus status = [change[NSKeyValueChangeNewKey] integerValue];
NSLog(#"status = %ld", (long)status);
}
}
I added KVO and got result that after click play player status is AVPlayerStatusUnknown. After that it is not changing despite the fact that the video is reproducing.
Does anyone have any idea?
set automaticallyWaitsToMinimizeStalling property of AVPlayer to false in order to start playback immediately.
AVPlayer *player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
player.automaticallyWaitsToMinimizeStalling = false;
But if sufficient content is not available for playing then player might stall.
for more details please refer this Apple documentation.
avplayer
Here is my code:
NSURL *fileURL = [NSURL URLWithString:#"http://myglams.com/UserUpload/vv/oSiFevyNHM.mp4"];
moviePlayerController = [[MPMoviePlayerController alloc] initWithContentURL:fileURL];
[moviePlayerController.view setFrame:CGRectMake(20, 100, 380, 150)];
[self.view addSubview:moviePlayerController.view];
moviePlayerController.fullscreen = YES;
moviePlayerController.allowsAirPlay = YES;
moviePlayerController.shouldAutoplay =YES;
moviePlayerController.controlStyle = MPMovieControlStyleEmbedded;
[moviePlayerController play];// cannot stream automatically its needs many play every time
The best option for play videos is AVPlayer and AVPlayerLayer using currentItem.loadedTimeRanges observer.
Example
void *kTimeRangesKVO1 = &kTimeRangesKVO1;
playerView = [[AVPlayer alloc] initWithURL:url];
layer = [AVPlayerLayer playerLayerWithPlayer:playerView];
layer.frame = CGRectMake(0, 0, videoContentPlayer.frame.size.width, videoContentPlayer.frame.size.height);
[layer setVideoGravity:AVLayerVideoGravityResizeAspect];
[videoContentPlayer.layer insertSublayer:layer below:labelTime.layer];
[playerView addObserver:self forKeyPath:#"currentItem.loadedTimeRanges" options:NSKeyValueObservingOptionNew context:kTimeRangesKVO1];
// the callback method
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (kTimeRangesKVO1 == context) {
NSArray *timeRanges = (NSArray*)[change objectForKey:NSKeyValueChangeNewKey];
if (timeRanges && [timeRanges count]) {
CMTimeRange timerange=[[timeRanges objectAtIndex:0]CMTimeRangeValue];
float currentBufferDuration = CMTimeGetSeconds(CMTimeAdd(timerange.start, timerange.duration));
CMTime duration = playerView.currentItem.asset.duration;
float seconds = CMTimeGetSeconds(duration);
//your code here with your rules.
}
}
}
Remember remove your observer with removeObserver something like [playerView removeObserver:self forKeyPath:#"currentItem.loadedTimeRanges"];
I am trying to play a movie at the beginning of my game. I am using AVPlayer to do this. My problem is, when I register a KVO to check the status of my AVPlayer, my game proceeds as usual without waiting for the video to load and finish. As a result, I can only hear the audio from my .mov file and can't see any video (since my game has already started).
I would like the video to load and finish before proceeding with the game.
Here's the code:
#interface RMVideoView : NSView
{
NSURL* _videoURL;
AVPlayer* _player;
AVPlayerLayer* _playerLayer;
}
#property (nonatomic, readonly, strong) AVPlayer* player;
#property (nonatomic, readonly, strong) AVPlayerLayer* playerLayer;
#property (nonatomic, retain) NSURL* videoURL;
- (void) play;
#end
static void *RMVideoViewPlayerLayerReadyForDisplay = &RMVideoViewPlayerLayerReadyForDisplay;
static void *RMVideoViewPlayerItemStatusContext = &RMVideoViewPlayerItemStatusContext;
#interface RMVideoView()
- (void)onError:(NSError*)error;
- (void)onReadyToPlay;
- (void)setUpPlaybackOfAsset:(AVAsset *)asset withKeys:(NSArray *)keys;
#end
#implementation RMVideoView
#synthesize player = _player;
#synthesize playerLayer = _playerLayer;
#synthesize videoURL = _videoURL;
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.wantsLayer = YES;
_player = [[AVPlayer alloc] init];
[self addObserver:self forKeyPath:#"player.currentItem.status" options:NSKeyValueObservingOptionNew context:RMVideoViewPlayerItemStatusContext];
}
return self;
}
- (void) setVideoURL:(NSURL *)videoURL {
_videoURL = videoURL;
[self.player pause];
[self.playerLayer removeFromSuperlayer];
AVURLAsset *asset = [AVAsset assetWithURL:self.videoURL];
[asset retain];
NSArray *assetKeysToLoadAndTest = [NSArray arrayWithObjects:#"playable", #"hasProtectedContent", #"tracks", #"duration", nil];
[asset loadValuesAsynchronouslyForKeys:assetKeysToLoadAndTest completionHandler:^{
dispatch_async(dispatch_get_main_queue(),^{
[self setUpPlaybackOfAsset:asset withKeys:assetKeysToLoadAndTest];
});
}];
}
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == RMVideoViewPlayerItemStatusContext)
{
AVPlayerStatus status = [[change objectForKey:NSKeyValueChangeNewKey] integerValue];
switch (status)
{
case AVPlayerItemStatusUnknown:
break;
case AVPlayerItemStatusReadyToPlay:
[self onReadyToPlay];
break;
case AVPlayerItemStatusFailed:
[self onError:nil];
break;
}
}
else if (context == RMVideoViewPlayerLayerReadyForDisplay)
{
if ([[change objectForKey:NSKeyValueChangeNewKey] boolValue])
{
self.playerLayer.hidden = NO;
}
}
else
{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
#pragma mark - Private
- (void)onError:(NSError*)error {
// Notify delegate
}
- (void)onReadyToPlay {
// Notify delegate
[self.player play];
}
- (void)setUpPlaybackOfAsset:(AVAsset *)asset withKeys:(NSArray *)keys {
for (NSString *key in keys) {
NSError *error = nil;
if ([asset statusOfValueForKey:key error:&error] == AVKeyValueStatusFailed) {
[self onError:error];
return;
}
}
if (!asset.isPlayable || asset.hasProtectedContent) {
[self onError:nil];
return;
}
if ([[asset tracksWithMediaType:AVMediaTypeVideo] count] != 0)
{ // Asset has video tracks
_playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
self.playerLayer.frame = self.layer.bounds;
self.playerLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
self.playerLayer.hidden = NO;
[self.layer addSublayer:self.playerLayer];
[self addObserver:self forKeyPath:#"playerLayer.readyForDisplay" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:RMVideoViewPlayerLayerReadyForDisplay];
}
// Create a new AVPlayerItem and make it our player's current item.
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
[self.player replaceCurrentItemWithPlayerItem:playerItem];
}
#pragma mark - Public
- (void) play {
[self.player play];
}
#end
I am calling the above code from my entry function's -(void)drawView method this way:
-(void)drawView
{
if(playVideo)
{
RMVideoView *rmVid = [[RMVideoView alloc]init];
NSURL* MovieURL;
NSBundle *bundle = [NSBundle mainBundle];
if(bundle != nil)
{
NSString *moviePath = [bundle pathForResource:#"MyVideoResource" ofType:#"mov"];
if (moviePath)
{
MovieURL = [NSURL fileURLWithPath:moviePath];
[MovieURL retain];
[rmVid setVideoURL:MovieURL];
}
}
playVideo = kFalse;
}
}
The call made to [rmVid setVideoURL:MovieURL] returns when KVO is setup and the game runs forward.
Please help!
You can listen for a notification, when the video playback reaches the end like this:
AVPlayerItem *avPlayerItem =[[AVPlayerItem alloc]initWithAsset:avAsset];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachedEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:avPlayerItem];
So I am building a custom video player using AVFoundation - AVPlayer and AVPlayerLayer.
Currently, all I want the player to do is play a video in the asset library with a hardcoded url to that video. I would like this to be contained in a SubClass of UIView so I can use it all around my app.
Here is my code so far:
CUPlayer.h:
#interface CUPlayer : UIView
{
AVPlayer *player;
AVPlayerLayer *playerLayer;
AVPlayerItem *item;
NSURL *url;
}
#property(nonatomic) UIViewAutoresizing autoresizingMask;
#end
CUPlayer.m:
#implementation CUPlayer
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
// Initialization code
self.backgroundColor = [UIColor redColor];
[self setupURL];
}
return self;
}
-(void)setupURL
{
NSLog(#"URL setting up");
NSString *string = #"assets-library://asset/asset.mov?id=0A937F0D-6265-452D-8800- 1A760E8E88B9&ext=mov";
url = [[NSURL alloc] initFileURLWithPath: string];
[self setupPlayerForURL];
}
-(void)setupPlayerForURL
{
AVAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
item = [AVPlayerItem playerItemWithAsset:asset];
player = [AVPlayer playerWithPlayerItem:item];
[player addObserver:self forKeyPath:#"status" options:0 context:nil];
playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
//player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
[self.layer addSublayer:playerLayer];
playerLayer.frame = CGRectMake(0, 0, 200, 200);
//[playerLayer setBackgroundColor:[UIColor greenColor].CGColor];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (object == player && [keyPath isEqualToString:#"status"]) {
if (player.status == AVPlayerStatusFailed) {
NSLog(#"AVPlayer Failed");
} else if (player.status == AVPlayerStatusReadyToPlay) {
NSLog(#"AVPlayer Ready to Play");
[player play];
} else if (player.status == AVPlayerItemStatusUnknown) {
NSLog(#"AVPlayer Unknown");
}
}
}
And then I am calling this from the View Controller:
CUPlayer *cuPlayer = [[CUPlayer alloc]initWithFrame:CGRectMake(0, 0, 250, 250)];
[self.view addSubview:cuPlayer];
This complies but just give me a red square without the video playing. The URL to the local file is definitely correct. I can get it working if I hold all the code in the view controller and run the play in -(void)viewDidLayoutSubviews.
Help would very much be appreciated, I have read all the documentation multiple times trying to work this thing out.
Tom