I'm writing Plugin for AVPlayer in ios. I need to know when user click on Done button in AVPlayerViewController ( I want to know when user close video) and I don't access to AVPlayerViewController object. I checked event and found only rate property in AVPlayer set to 0 but in pause situation also the rate set to 0 . how I figure out these two situations ?
thanks all.
I met the problem when I was developing the player in windowed mode. It is currently impossible without tricks. Therefore, I used KVO to observe contentOverlayView which is actually a full-size view of AVPlayerViewController. Code is a bit complicated. In the example below, playerView property is a view from xib/storyboard on the view controller (see attached).
#import <AVKit/AVKit.h>
#import <AVFoundation/AVFoundation.h>
static NSString * const kBoundsProperty = #"bounds";
static void * kBoundsContext = &kBoundsContext;
#interface ViewController ()
#property (nonatomic, strong) AVPlayerViewController *playerViewController;
// View for windowed mode.
#property (weak, nonatomic) IBOutlet UIView *playerView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.playerViewController = [[AVPlayerViewController alloc] init];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// Because in -viewDidLoad frame is unknown.
[self loadPlayerView];
}
- (void)loadPlayerView {
NSURL *videoURL = [NSURL URLWithString:#"https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"];
AVPlayer *player = [[AVPlayer alloc] initWithURL:videoURL];
self.playerViewController.player = player;
[player play];
[self addChildViewController:self.playerViewController];
[self.playerView addSubview:self.playerViewController.view];
// MARK: I would recommend to use constraints instead of frame.
self.playerViewController.view.frame = self.playerView.bounds;
[self.playerViewController.contentOverlayView addObserver:self forKeyPath:kBoundsProperty options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:kBoundsContext];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if (context == kBoundsContext) {
CGRect oldBounds = [change[NSKeyValueChangeOldKey] CGRectValue];
CGRect newBounds = [change[NSKeyValueChangeNewKey] CGRectValue];
BOOL wasFullscreen = CGRectEqualToRect(oldBounds, [UIScreen mainScreen].bounds);
BOOL isFullscreen = CGRectEqualToRect(newBounds, [UIScreen mainScreen].bounds);
if (isFullscreen && !wasFullscreen) {
if (CGRectEqualToRect(oldBounds, CGRectMake(0.0, 0.0, newBounds.size.height, newBounds.size.width))) {
NSLog(#"Rotated fullscreen");
} else {
NSLog(#"Entered fullscreen");
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:#"DidEnterInFullscreen" object:nil];
});
}
} else if (!isFullscreen && wasFullscreen) {
NSLog(#"Exited fullscreen");
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:#"DidExitFromFullscreen" object:nil];
});
}
}
}
#end
Related
I tried to play video advertisements using the Custom Vast tag in the Google IMA integration application on macOS 12.0.1 and Xcode 13.1 but it gives the below error. I tried somany ways to but couldn't fix it. info.plist file I have shown below.
AdvancedExample[1476:21763] Error loading ads: Ads cannot be requested
because this AdsLoader failed to load
info.plist
VideoViewController.m
#import "VideoViewController.h"
#import AVFoundation;
#import "Constants.h"
typedef enum { PlayButton, PauseButton } PlayButtonType;
#interface VideoViewController () <AVPictureInPictureControllerDelegate, IMAAdsLoaderDelegate,
IMAAdsManagerDelegate, UIAlertViewDelegate>
// Tracking for play/pause.
#property(nonatomic) BOOL isAdPlayback;
// Play/Pause buttons.
#property(nonatomic, strong) UIImage *playBtnBG;
#property(nonatomic, strong) UIImage *pauseBtnBG;
// PiP objects.
#property(nonatomic, strong) AVPictureInPictureController *pictureInPictureController;
#property(nonatomic, strong) IMAPictureInPictureProxy *pictureInPictureProxy;
// Storage points for resizing between fullscreen and non-fullscreen
/// Frame for video player in fullscreen mode.
#property(nonatomic, assign) CGRect fullscreenVideoFrame;
/// Frame for video view in portrait mode.
#property(nonatomic, assign) CGRect portraitVideoViewFrame;
/// Frame for video player in portrait mode.
#property(nonatomic, assign) CGRect portraitVideoFrame;
/// Frame for controls in fullscreen mode.
#property(nonatomic, assign) CGRect fullscreenControlsFrame;
/// Frame for controls view in portrait mode.
#property(nonatomic, assign) CGRect portraitControlsViewFrame;
/// Frame for controls in portrait mode.
#property(nonatomic, assign) CGRect portraitControlsFrame;
/// Option for tracking fullscreen.
#property(nonatomic, assign) BOOL fullscreen;
/// Option for tracking load event
#property(nonatomic, assign) BOOL didRequestAds;
/// Gesture recognizer for tap on video.
#property(nonatomic, strong) UITapGestureRecognizer *videoTapRecognizer;
// IMA objects.
#property(nonatomic, strong) IMAAdsManager *adsManager;
#property(nonatomic, strong) IMACompanionAdSlot *companionSlot;
// Content player objects.
#property(nonatomic, strong) AVPlayer *contentPlayer;
#property(nonatomic, strong) AVPlayerLayer *contentPlayerLayer;
#property(nonatomic, strong) id playHeadObserver;
#end
#implementation VideoViewController
#pragma mark Set-up methods
// Set up the new view controller.
- (void)viewDidLoad {
[super viewDidLoad];
[self.topLabel setText:self.video.title];
// Set the play button image.
self.playBtnBG = [UIImage imageNamed:#"play.png"];
// Set the pause button image.
self.pauseBtnBG = [UIImage imageNamed:#"pause.png"];
self.isAdPlayback = NO;
self.fullscreen = NO;
self.didRequestAds = NO;
// Fix iPhone issue of log text starting in the middle of the UITextView
self.automaticallyAdjustsScrollViewInsets = NO;
// Set up CGRects for resizing the video and controls on rotate.
CGRect videoViewBounds = self.videoView.bounds;
self.portraitVideoViewFrame = self.videoView.frame;
self.portraitVideoFrame =
CGRectMake(0, 0, videoViewBounds.size.width, videoViewBounds.size.height);
CGRect videoControlsBounds = self.videoControls.bounds;
self.portraitControlsViewFrame = self.videoControls.frame;
self.portraitControlsFrame =
CGRectMake(0, 0, videoControlsBounds.size.width, videoControlsBounds.size.height);
// Set videoView on top of everything else (for fullscreen support).
[self.view bringSubviewToFront:self.videoView];
[self.view bringSubviewToFront:self.videoControls];
// Check orientation, set to fullscreen if we're in landscape
if ([[UIDevice currentDevice] orientation] == UIDeviceOrientationLandscapeLeft ||
[[UIDevice currentDevice] orientation] == UIDeviceOrientationLandscapeRight) {
[self viewDidEnterLandscape];
}
// Set up content player and IMA classes, then request ads. If the user selected "Custom",
// get the ad tag from the pop-up dialog.
[self setUpContentPlayer];
[self setUpIMA];
}
// Makes the request on first appearance only.
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (self.didRequestAds) {
return;
}
self.didRequestAds = YES;
if ([self.video.tag isEqual:#"custom"]) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Tag"
message:#"Enter your test tag below"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
alert.alertViewStyle = UIAlertViewStylePlainTextInput;
[alert show];
} else {
[self requestAdsWithTag:self.video.tag];
}
}
- (void)viewWillDisappear:(BOOL)animated {
[self.contentPlayer pause];
// Don't reset if we're presenting a modal view (for example, in-app clickthrough).
if ([self.navigationController.viewControllers indexOfObject:self] == NSNotFound) {
if (self.adsManager) {
[self.adsManager destroy];
self.adsManager = nil;
}
[self removeObservers];
self.contentPlayer = nil;
}
[super viewWillDisappear:animated];
}
//Remove ContentPlayer Observer
- (void)removeObservers {
if (self.playHeadObserver) {
[self.contentPlayer removeTimeObserver:self.playHeadObserver];
self.playHeadObserver = nil;
}
#try {
[self.contentPlayer removeObserver:self forKeyPath:#"rate"];
[self.contentPlayer removeObserver:self forKeyPath:#"currentItem.duration"];
} #catch (NSException *exception) { }
}
// If pop-up dialog was shown, request ads with provided tag on dialog close.
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
[self requestAdsWithTag:[[alertView textFieldAtIndex:0] text]];
}
// Initialize the content player and load content.
- (void)setUpContentPlayer {
// Load AVPlayer with path to our content.
NSURL *contentURL = [NSURL URLWithString:self.video.video];
self.contentPlayer = [AVPlayer playerWithURL:contentURL];
// Playhead observers for progress bar.
__weak VideoViewController *controller = self;
self.playHeadObserver = [controller.contentPlayer
addPeriodicTimeObserverForInterval:CMTimeMake(1, 30)
queue:NULL
usingBlock:^(CMTime time) {
CMTime duration = [controller
getPlayerItemDuration:controller.contentPlayer.currentItem];
[controller updatePlayHeadWithTime:time duration:duration];
}];
[self.contentPlayer addObserver:self forKeyPath:#"rate" options:0 context:#"contentPlayerRate"];
[self.contentPlayer addObserver:self
forKeyPath:#"currentItem.duration"
options:0
context:#"playerDuration"];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(contentDidFinishPlaying:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:self.contentPlayer.currentItem];
// Set up fullscreen tap listener to show controls.
self.videoTapRecognizer =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(showFullscreenControls:)];
[self.videoView addGestureRecognizer:self.videoTapRecognizer];
// Create a player layer for the player.
self.contentPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.contentPlayer];
// Size, position, and display the AVPlayer.
self.contentPlayerLayer.frame = self.videoView.layer.bounds;
[self.videoView.layer addSublayer:self.contentPlayerLayer];
// Set ourselves up for PiP.
self.pictureInPictureProxy =
[[IMAPictureInPictureProxy alloc] initWithAVPictureInPictureControllerDelegate:self];
self.pictureInPictureController =
[[AVPictureInPictureController alloc] initWithPlayerLayer:self.contentPlayerLayer];
self.pictureInPictureController.delegate = self.pictureInPictureProxy;
if (![AVPictureInPictureController isPictureInPictureSupported] && self.pictureInPictureButton) {
self.pictureInPictureButton.hidden = YES;
}
}
// Handler for keypath listener that is added for content playhead observer.
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if (context == #"contentPlayerRate" && self.contentPlayer == object) {
[self updatePlayHeadState:(self.contentPlayer.rate != 0)];
} else if (context == #"playerDuration" && self.contentPlayer == object) {
[self
updatePlayHeadDurationWithTime:[self getPlayerItemDuration:self.contentPlayer.currentItem]];
}
}
#pragma mark UI handlers
// Handle clicks on play/pause button.
- (IBAction)onPlayPauseClicked:(id)sender {
if (!self.isAdPlayback) {
if (self.contentPlayer.rate == 0) {
[self.contentPlayer play];
} else {
[self.contentPlayer pause];
}
} else {
if (self.playHeadButton.tag == PlayButton) {
[self.adsManager resume];
[self setPlayButtonType:PauseButton];
} else {
[self.adsManager pause];
[self setPlayButtonType:PlayButton];
}
}
}
// Updates play button for provided playback state.
- (void)updatePlayHeadState:(BOOL)isPlaying {
[self setPlayButtonType:isPlaying ? PauseButton : PlayButton];
}
// Sets play button type.
- (void)setPlayButtonType:(PlayButtonType)buttonType {
self.playHeadButton.tag = buttonType;
[self.playHeadButton setImage:buttonType == PauseButton ? self.pauseBtnBG : self.playBtnBG
forState:UIControlStateNormal];
}
// Called when the user seeks.
- (IBAction)playHeadValueChanged:(id)sender {
if (![sender isKindOfClass:[UISlider class]]) {
return;
}
if (self.isAdPlayback == NO) {
UISlider *slider = (UISlider *)sender;
// If the playhead value changed by the user, skip to that point of the
// content is skippable.
[self.contentPlayer seekToTime:CMTimeMake(slider.value, 1)];
}
}
// Used to track progress of ads for progress bar.
- (void)adDidProgressToTime:(NSTimeInterval)mediaTime totalTime:(NSTimeInterval)totalTime {
CMTime time = CMTimeMakeWithSeconds(mediaTime, 1000);
CMTime duration = CMTimeMakeWithSeconds(totalTime, 1000);
[self updatePlayHeadWithTime:time duration:duration];
self.progressBar.maximumValue = totalTime;
}
// Get the duration value from the player item.
- (CMTime)getPlayerItemDuration:(AVPlayerItem *)item {
CMTime itemDuration = kCMTimeInvalid;
if ([item respondsToSelector:#selector(duration)]) {
itemDuration = item.duration;
} else {
if (item.asset && [item.asset respondsToSelector:#selector(duration)]) {
// Sometimes the test app hangs here for ios 4.2.
itemDuration = item.asset.duration;
}
}
return itemDuration;
}
// Updates progress bar for provided time and duration.
- (void)updatePlayHeadWithTime:(CMTime)time duration:(CMTime)duration {
if (CMTIME_IS_INVALID(time)) {
return;
}
Float64 currentTime = CMTimeGetSeconds(time);
if (isnan(currentTime)) {
return;
}
self.progressBar.value = currentTime;
self.playHeadTimeText.text =
[[NSString alloc] initWithFormat:#"%d:%02d", (int)currentTime / 60, (int)currentTime % 60];
[self updatePlayHeadDurationWithTime:duration];
}
// Update the current playhead duration.
- (void)updatePlayHeadDurationWithTime:(CMTime)duration {
if (CMTIME_IS_INVALID(duration)) {
return;
}
Float64 durationValue = CMTimeGetSeconds(duration);
if (isnan(durationValue)) {
return;
}
self.progressBar.maximumValue = durationValue;
self.durationTimeText.text = [[NSString alloc]
initWithFormat:#"%d:%02d", (int)durationValue / 60, (int)durationValue % 60];
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
switch (interfaceOrientation) {
case UIInterfaceOrientationLandscapeLeft:
case UIInterfaceOrientationLandscapeRight:
[self viewDidEnterPortrait];
break;
case UIInterfaceOrientationPortrait:
case UIInterfaceOrientationPortraitUpsideDown:
[self viewDidEnterLandscape];
break;
case UIInterfaceOrientationUnknown:
break;
}
}
- (void)viewDidEnterLandscape {
self.fullscreen = YES;
CGRect screenRect = [[UIScreen mainScreen] bounds];
if ([[[UIDevice currentDevice] systemVersion] floatValue] < 8.0) {
self.fullscreenVideoFrame = CGRectMake(0, 0, screenRect.size.height, screenRect.size.width);
self.fullscreenControlsFrame =
CGRectMake(0, (screenRect.size.width - self.videoControls.frame.size.height),
screenRect.size.height, self.videoControls.frame.size.height);
} else {
self.fullscreenVideoFrame = CGRectMake(0, 0, screenRect.size.width, screenRect.size.height);
self.fullscreenControlsFrame =
CGRectMake(0, (screenRect.size.height - self.videoControls.frame.size.height),
screenRect.size.width, self.videoControls.frame.size.height);
}
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
[[self navigationController] setNavigationBarHidden:YES];
self.videoView.frame = self.fullscreenVideoFrame;
self.contentPlayerLayer.frame = self.fullscreenVideoFrame;
self.videoControls.frame = self.fullscreenControlsFrame;
self.videoControls.hidden = YES;
}
- (void)viewDidEnterPortrait {
self.fullscreen = NO;
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone];
[[self navigationController] setNavigationBarHidden:NO];
self.videoView.frame = self.portraitVideoViewFrame;
self.contentPlayerLayer.frame = self.portraitVideoFrame;
self.videoControls.frame = self.portraitControlsViewFrame;
}
- (IBAction)videoControlsTouchStarted:(id)sender {
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:#selector(hideFullscreenControls)
object:self];
}
- (IBAction)videoControlsTouchEnded:(id)sender {
[self startHideControlsTimer];
}
- (void)showFullscreenControls:(UITapGestureRecognizer *)recognizer {
if (self.fullscreen) {
self.videoControls.hidden = NO;
self.videoControls.alpha = 0.9;
[self startHideControlsTimer];
}
}
- (void)startHideControlsTimer {
[self performSelector:#selector(hideFullscreenControls) withObject:self afterDelay:3];
}
- (void)hideFullscreenControls {
[UIView animateWithDuration:0.5
animations:^{
self.videoControls.alpha = 0.0;
}];
}
- (IBAction)onPipButtonClicked:(id)sender {
if ([self.pictureInPictureController isPictureInPictureActive]) {
[self.pictureInPictureController stopPictureInPicture];
} else {
[self.pictureInPictureController startPictureInPicture];
}
}
#pragma mark IMA SDK methods
// Initialize ad display container.
- (IMAAdDisplayContainer *)createAdDisplayContainer {
// Create our AdDisplayContainer. Initialize it with our videoView as the container. This
// will result in ads being displayed over our content video.
if (self.companionView) {
// MOE:strip_line [START ad_display_container_init]
return [[IMAAdDisplayContainer alloc] initWithAdContainer:self.videoView
viewController:self
companionSlots:#[ self.companionSlot ]];
// [END ad_display_container_init] MOE:strip_line
} else {
return [[IMAAdDisplayContainer alloc] initWithAdContainer:self.videoView
viewController:self
companionSlots:nil];
}
}
// Register companion slots.
- (void)setUpCompanions {
// MOE:strip_line [START companion_slot_declaration]
self.companionSlot =
[[IMACompanionAdSlot alloc] initWithView:self.companionView
width:self.companionView.frame.size.width
height:self.companionView.frame.size.height];
// [END companion_slot_declaration] MOE:strip_line
}
// Initialize AdsLoader.
- (void)setUpIMA {
if (self.adsManager) {
[self.adsManager destroy];
}
[self.adsLoader contentComplete];
self.adsLoader.delegate = self;
if (self.companionView) {
[self setUpCompanions];
}
}
// Request ads for provided tag.
- (void)requestAdsWithTag:(NSString *)adTagUrl {
[self logMessage:#"Requesting ads"];
// Create an ad request with our ad tag, display container, and optional user context.
IMAAdsRequest *request = [[IMAAdsRequest alloc]
initWithAdTagUrl:adTagUrl
adDisplayContainer:[self createAdDisplayContainer]
avPlayerVideoDisplay:[[IMAAVPlayerVideoDisplay alloc] initWithAVPlayer:self.contentPlayer]
pictureInPictureProxy:self.pictureInPictureProxy
userContext:nil];
[self.adsLoader requestAdsWithRequest:request];
}
// Notify IMA SDK when content is done for post-rolls.
- (void)contentDidFinishPlaying:(NSNotification *)notification {
// Make sure we don't call contentComplete as a result of an ad completing.
if (notification.object == self.contentPlayer.currentItem) {
[self.adsLoader contentComplete];
}
}
#pragma mark AdsLoader Delegates
- (void)adsLoader:(IMAAdsLoader *)loader adsLoadedWithData:(IMAAdsLoadedData *)adsLoadedData {
// Grab the instance of the IMAAdsManager and set ourselves as the delegate.
self.adsManager = adsLoadedData.adsManager;
self.adsManager.delegate = self;
// Create ads rendering settings to tell the SDK to use the in-app browser.
IMAAdsRenderingSettings *adsRenderingSettings = [[IMAAdsRenderingSettings alloc] init];
adsRenderingSettings.linkOpenerPresentingController = self;
// Initialize the ads manager.
[self.adsManager initializeWithAdsRenderingSettings:adsRenderingSettings];
}
- (void)adsLoader:(IMAAdsLoader *)loader failedWithErrorData:(IMAAdLoadingErrorData *)adErrorData {
// Something went wrong loading ads. Log the error and play the content.
[self logMessage:#"Error loading ads: %#", adErrorData.adError.message];
self.isAdPlayback = NO;
[self setPlayButtonType:PauseButton];
[self.contentPlayer play];
}
#pragma mark AdsManager Delegates
/*- (void)adsManager:(IMAAdsManager *)adsManager didReceiveAdEvent:(IMAAdEvent *)event {
[self logMessage:#"AdsManager event (%#).", event.typeString];
// When the SDK notified us that ads have been loaded, play them.
switch (event.type) {
case kIMAAdEvent_LOADED:
if (![self.pictureInPictureController isPictureInPictureActive]) {
[adsManager start];
}
break;
case kIMAAdEvent_PAUSE:
[self setPlayButtonType:PlayButton];
break;
case kIMAAdEvent_RESUME:
[self setPlayButtonType:PauseButton];
break;
case kIMAAdEvent_TAPPED:
[self showFullscreenControls:nil];
break;
default:
break;
}
}*/
- (void)adsManager:(IMAAdsManager *)adsManager didReceiveAdEvent:(IMAAdEvent *)event {
[self logMessage:#"AdsManager event (%#).", event.typeString];
switch (event.type) {
case kIMAAdEvent_LOADED:
[adsManager start];
break;
//case kIMAAdEvent_STARTED: {
break;
case kIMAAdEvent_PAUSE:
[self setPlayButtonType:PlayButton];
break;
case kIMAAdEvent_RESUME:
[self setPlayButtonType:PauseButton];
break;
case kIMAAdEvent_TAPPED:
[self showFullscreenControls:nil];
break;
default:
break;
}
}
- (void)adsManager:(IMAAdsManager *)adsManager didReceiveAdError:(IMAAdError *)error {
// Something went wrong with the ads manager after ads were loaded. Log the error and play the
// content.
[self logMessage:#"AdsManager error: %#", error.message];
self.isAdPlayback = NO;
[self setPlayButtonType:PauseButton];
[self.contentPlayer play];
}
- (void)adsManagerDidRequestContentPause:(IMAAdsManager *)adsManager {
// The SDK is going to play ads, so pause the content.
self.isAdPlayback = YES;
[self setPlayButtonType:PauseButton];
[self.contentPlayer pause];
}
- (void)adsManagerDidRequestContentResume:(IMAAdsManager *)adsManager {
// The SDK is done playing ads (at least for now), so resume the content.
self.isAdPlayback = NO;
[self setPlayButtonType:PauseButton];
[self.contentPlayer play];
}
#pragma mark Utility methods
- (void)logMessage:(NSString *)log, ... {
va_list args;
va_start(args, log);
NSString *s = [[NSString alloc] initWithFormat:[[NSString alloc] initWithFormat:#"%#\n", log]
arguments:args];
self.consoleView.text = [self.consoleView.text stringByAppendingString:s];
NSLog(#"%#", s);
va_end(args);
if (self.consoleView.text.length > 0) {
NSRange bottom = NSMakeRange(self.consoleView.text.length - 1, 1);
[self.consoleView scrollRangeToVisible:bottom];
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
I have a split view controller set up as a video player. The master view is a list of available videos and the detail view should play the selected video from the list.
I'm able to send the required properties to the detail view from the master (url, start time, end time...) but I'm only getting audio.
When a selection is made from the master view a delegate -(void)didselectVid: method gets fired in the detail view.
- (void)viewDidLoad
{
[super viewDidLoad];
if (selectedBool) {
timer = [NSTimer
scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(timedJob)
userInfo:nil
repeats:YES];
[timer fire];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(moviePlayerPlaybackStateDidChange:) name:MPMoviePlayerPlaybackStateDidChangeNotification object:nil];
NSString * videoStr = [NSString stringWithFormat:#"%#/%#", gameVideoURL , videoUrlStr];
NSString * urlStrEscaped = [videoStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL * dataURL = [NSURL URLWithString:urlStrEscaped];
moviePlayerCrl.movieSourceType = MPMovieSourceTypeStreaming;
moviePlayerCrl = [[MPMoviePlayerController alloc] initWithContentURL:dataURL];
[self registerForMovieNotifications];
}
}
- (void)registerForMovieNotifications
{
//movie is ready to play notifications
if ([self.moviePlayerCrl respondsToSelector:#selector(loadState)])
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(moviePlayerLoadStateChanged:) name:MPMoviePlayerLoadStateDidChangeNotification object:nil];
[self.moviePlayerCrl prepareToPlay];
}
//movie has finished notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(moviePlayBackDidFinish:) name:MPMoviePlayerPlaybackDidFinishNotification object:nil];
}
- (void)moviePlayerLoadStateChanged:(NSNotification*)notification
{
NSLog(#"load state changed");
//unless state is unknown, start playback
if ([self.moviePlayerCrl loadState] != MPMovieLoadStateUnknown)
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerLoadStateDidChangeNotification object:nil];
[self.moviePlayerCrl.view setFrame: CGRectMake(0,
[UIApplication sharedApplication].statusBarFrame.size.height
+ self.navigationController.navigationBar.frame.size.height
+ self.navigationController.toolbar.frame.size.height,
self.view.bounds.size.width,
self.view.bounds.size.height
- [UIApplication sharedApplication].statusBarFrame.size.height
- self.navigationController.navigationBar.frame.size.height
- self.navigationController.toolbar.frame.size.height
- self.tabBarController.tabBar.frame.size.height
)]; // x, y, width, height
[self.view addSubview:self.moviePlayerCrl.view];
[self.view bringSubviewToFront:self.moviePlayerCrl.view];
[self.moviePlayerCrl setFullscreen:NO animated:YES];
[self.moviePlayerCrl play];
[moviePlayerCrl setCurrentPlaybackTime:startPlaybackTime];
}
}
-(void)didselectVid:(VideoClipData *)vid
{
videoUrlStr = vid.evtUrlStr;
startTime = vid.evtStartStr;
endTime = vid.evtEndTimeStr;
gameNamesStr = vid.evtNameStr;
double startTimeInterval = [startTime doubleValue];
double endTimeInterval = [endTime doubleValue];
startPlaybackTime = startTimeInterval;
endPlaybackTime = endTimeInterval;
selectedBool = YES;
[self viewDidLoad];
}
The parent view frame and the MoviePlayerController view's frame have to be same before making MoviePlayerController.view a subview.
i.e Have one more view inside your master view say playerContainerView. Before adding the moviePlayerCrl.view as the subview of playerContainerView, set the playerContainerView and moviePlayerCrl.view to the same frame.
This should work.
I was able to fix this by calling didselectVid differently. With what I assume is proper split view delegation:
in master view .h :
#import "VideoClipData.h"
#protocol DetailDelegate <NSObject>
-(void)didselectEvtVid:(VideoClipData *) vid;
#end
#import <UIKit/UIKit.h>
#class DetailView;
#interface MasterView : UIViewController
#property (strong, nonatomic) DetailView *detailViewController;
#property (nonatomic, unsafe_unretained) id<DetailDelegate> delegate;
master view .m:
- (void)viewDidLoad {
[super viewDidLoad];
self.detailViewController = (DetailView *)[[self.splitViewController.viewControllers lastObject] topViewController];
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.detailViewController didselectEvtVid:gameVideos];
}
And instead of calling viewDidLoad again I called [self configureView]; as given in apple's split view example.
I'm trying to use the library https://github.com/arturfelipet/AnimatedLogin
I created my own project and wrote exactly the same code as in their example (but in my project I used Storyboard), but the video only shows a black screen. Here's the way I create the MPMoviePlayerController:
#import <AVFoundation/AVFoundation.h>
#import <MediaPlayer/MediaPlayer.h>
#import <QuartzCore/QuartzCore.h>
#interface ViewController () {
MPMoviePlayerController *player;
}
#property (nonatomic, strong) MPMoviePlayerController *player;
#end
#implementation ViewController
#synthesize player;
- (void)viewDidLoad
{
[super viewDidLoad];
CGRect screen = [[UIScreen mainScreen] bounds];
NSURL *movieUrl = [[NSBundle mainBundle] URLForResource:#"background" withExtension:#"mp4"];
self.player = [[MPMoviePlayerController alloc] initWithContentURL:movieUrl];
player.view.frame = screen;
player.scalingMode = MPMovieScalingModeFill;
[self.player setControlStyle:MPMovieControlStyleNone];
[self.view addSubview:player.view];
[player prepareToPlay];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playVideo)
name:MPMoviePlayerReadyForDisplayDidChangeNotification
object:player];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(moviePlayerDidFinish:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:self.player];
[player play];
}
- (void)moviePlayerDidFinish:(NSNotification *)note
{
if (note.object == self.player) {
NSInteger reason = [[note.userInfo objectForKey:MPMoviePlayerPlaybackDidFinishReasonUserInfoKey] integerValue];
if (reason == MPMovieFinishReasonPlaybackEnded)
{
[self.player play];
}
}
}
-(void)playVideo{
[player play];
}
#end
But it's only a black screen when the app shows up. I'm using this code in my LoginViewController which presents from App Delegate like this:
if (![PFUser currentUser])
self.window.rootViewController = [UIStoryboard instansiateVCWithClass:[TPLoginViewController class]];
Maybe it has to do something with that? I've tried so many things and would really appreciate an answer.
Thanks.
That is because you doing all initializing in your viewDidLoad. Which means that processing will block your screen until all the task are done completely.
Put that code in a method and call it after delay using:
[self performSelector:<#(SEL)#> withObject:<#(id)#> afterDelay:<#(NSTimeInterval)#>];
EDIT: As turned out after checking with #tracifycray, there was some problem with URL,it was turning out nil somehow. As he said, "Yes, I got it to work. I tried to use a .mov-file instead, and then it worked".
I have a 15 second animation in my app that is currently way too inefficient to be workable as it uses 1.5GB of RAM.
Am I using the animation properties of UIImageView incorrectly? Perhaps there is a different solution. All I want is for it to loop for a certain duration.
There will be improvements I can make to the 539 images to make them more efficient, but they still need to be retina size etc. Currently it is 30fps.
- (void)viewDidLoad {
[super viewDidLoad];
_animationFrames = [[NSMutableArray alloc] init];
for (long l = 0; l < 540; l++) {
NSString *frameNumber = [NSMutableString stringWithFormat:#"AnimationFrames-%04ld", l];
UIImage *frame = [UIImage imageNamed:frameNumber];
[_animationFrames addObject:frame];
}
UIImageView *animation = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
animation.animationImages = _tutorialFrames;
animation.animationDuration = 14;
[self.view addSubview: animation];
[animation startAnimating];
}
Thanks to Mirko Brunner's comments I discovered that I could use an embedded movie rather than play the animation through UIImageView, though I ended up going with AVPlayer rather than MPMoviePlayer.
#import "VideoViewController.h"
#import AVFoundation;
#interface VideoViewController ()
#property (weak, nonatomic) AVPlayer *avPlayer;
#property (weak, nonatomic) AVPlayerLayer *avPlayerLayer;
#end
#implementation VideoViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self avPlayer];
[self.avPlayer play];
}
- (AVPlayer *)avPlayer {
if (!_avPlayer) {
NSURL *url = [[NSBundle mainBundle]
URLForResource: #"Video" withExtension:#"mp4"];
_avPlayer = [AVPlayer playerWithURL:url];
_avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:_avPlayer];
_avPlayerLayer.frame = self.view.layer.bounds;
[self.view.layer addSublayer: _avPlayerLayer];
_avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[_avPlayer currentItem]];
}
return _avPlayer;
}
- (void)playerItemDidReachEnd:(NSNotification *)notification {
AVPlayerItem *p = [notification object];
[p seekToTime:kCMTimeZero];
}
I am working on an app that will let me play different videos on the iPad remotely with an iPhone. I have been following along with apples example for a video player but I've been having some troubles. The videos play just fine and I can get it to play from a variety of videos but switching between them a few times it will crash and i get this in the debugger:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'An AVPlayerItem cannot be associated with more than one instance of AVPlayer'
*** First throw call stack:
(0x380da8bf 0x37c261e5 0x30acbcb5 0x30abc1f7 0x30ac3bf3 0x30c93d55 0x30c95f7b 0x380ad2dd 0x380304dd 0x380303a5 0x37e07fcd 0x31bb0743 0x25e5 0x257c)
This is the code I am using to create the player:
MPMoviePlayerController *player = [[MPMoviePlayerController alloc] initWithContentOfURL:movieURL];
if (player) {
[self setMoviePlayerController:player];
[self installMovieNotificationObservers];
[player setContentURL:movieURL];
[player setMovieSourceType:sourceType];
[self applyUserSettingsToMoviePlayer];
[self.view addSubview:self.backgroundView];
[player.view setFrame:self.view.bounds];
[player.view setBackgroundColor = [UIColor blackColor];
[self.view addSubview:player.view];
}
And when the current movie is stopped I use:
[[self moviePlayerController] stop];
MPMoviePlayerController *player = [self moviePlayerController];
[player.view removeFromSuperview];
[self removeMovieNotificationHandlers];
[self setMoviePlayerController:nil];
Edit:
So Ive now discovered it happens every time i try and switch a video for the 11th time. weird! I'm practically pulling my hair out.
What fixed this problem for me was stopping the MPMoviePlayerController before doing the setContentURL.
MPMoviePlayerController *streamPlayer;
[streamPlayer stop];
[streamPlayer setContentURL:[NSURL URLWithString:selectedStation]];
In the implementation you have above, ARC doesn't know that the MPMoviePlayerController is finished and needs to be released.
Define MPMoviePlayerController in your .h file and make it accessible via a #property (and #synthesize).
#property (strong, nonatomic) MPMoviePlayerController * moviePlayerController;
Then take the result of your alloc & init and assign it to that. I.E.
self.moviePlayerController = [[MPMoviePlayerController alloc] initWithContentOfURL:movieURL];
you should just keep the moviePlayerController and if you want to play another video, just use
[self.moviePlayerController setContentURL:movieURL];
then in your notification callback:
- (void) moviePlayBackDidFinish:(NSNotification*)notification
{
self.moviePlayer = nil;
[self initanothermovieplayerandplay];
}
and please do not remove the notification handler from notification center, only do this in dealloc method of your VC.
now let's add some fade when the movie play is done:
- (void) moviePlayBackDidFinish:(NSNotification*)notification
{
[UIView animateWithDuration:1
delay: 0.0
options: UIViewAnimationOptionCurveEaseIn
animations:^{
// one second to fade out the view
self.moviePlayer.view.alpha = 0.0;
}
completion:^(BOOL finished){
self.moviePlayer = nil;
[self initanothermovieplayerandplay];
}
}
I had exactly the same problem.
Nothing was wrong with my and i guess with your code :)
Just a broken video file was mine problem.
Changing *.mov type to m4a for example fixed it. Maybe one or more of the files you play are corrupted?
Try to find out which files lead to crash and than if u can try to quickly forward backward the play position of one of them while playing - this should lead to crash in few tries.
This is how i found the bad files. By the way all my bad files were movies .mov made with Snapz Pro X :)
Not sure if it is the case here, but we had a lot of problems, because the MPMoviePlayer is a singleton somewhere under the hood.
What we did is, that we implemented our own MoviePlayer wrapper which can be used from UIView (actually we have exactly one subclass of UIView MoviePlayerView to show movies) and assures that only one instance of MPMoviePlayerController exists. The code goes like this (it contains some special stuff, we need to show previews/thumbs the way we want etc. you should clean up as well as some release-statements):
// MoviePlayer.h
#import <Foundation/Foundation.h>
#import <MediaPlayer/MediaPlayer.h>
#import "Logger.h"
#class MoviePlayerView;
#interface MoviePlayer : NSObject
{
#private
MPMoviePlayerController *controller;
MoviePlayerView *currentView;
}
#property (nonatomic, readonly) MPMoviePlayerController *controller;
+(MoviePlayer *) instance;
-(void) playMovie:(NSURL*)movieURL onView:(MoviePlayerView *)view;
-(void) stopMovie;
#end
// MoviePlayer.m
#import "MoviePlayer.h"
#import "MoviePlayerView.h"
#implementation MoviePlayer
#synthesize controller;
static MoviePlayer *player = nil;
#pragma mark Singleton management
+(MoviePlayer *) instance
{
#synchronized([MoviePlayer class])
{
if (player == nil)
{
player = [[super allocWithZone:NULL] init];
player->controller = [[MPMoviePlayerController alloc] init];
player->controller.shouldAutoplay = NO;
player->controller.scalingMode = MPMovieScalingModeAspectFit;
player->currentView = nil;
}
return player;
}
}
+(id) allocWithZone:(NSZone *)zone
{
return [[self instance] retain];
}
-(id) copyWithZone:(NSZone *)zone
{
return self;
}
-(id) retain
{
return self;
}
-(NSUInteger) retainCount
{
return NSUIntegerMax;
}
-(oneway void) release
{
// singleton will never be released
}
-(id) autorelease
{
return self;
}
#pragma mark MoviePlayer implementations
-(void) stopMovie
{
#synchronized(self)
{
if (controller.view.superview)
{
[controller.view removeFromSuperview];
}
if (controller.playbackState != MPMoviePlaybackStateStopped)
{
[controller pause];
[controller stop];
}
if (currentView)
{
NSNotificationCenter *ntfc = [NSNotificationCenter defaultCenter];
[ntfc removeObserver:currentView name:MPMoviePlayerLoadStateDidChangeNotification object:controller];
[ntfc removeObserver:currentView name:MPMoviePlayerPlaybackStateDidChangeNotification object:controller];
currentView = nil;
}
}
}
-(void) playMovie:(NSURL*)movieURL onView:(MoviePlayerView *)view
{
#synchronized(self)
{
[self stopMovie];
currentView = view;
NSNotificationCenter *ntfc = [NSNotificationCenter defaultCenter];
[ntfc addObserver:currentView
selector:#selector(loadStateDidChange:)
name:MPMoviePlayerLoadStateDidChangeNotification
object:controller];
[ntfc addObserver:currentView
selector:#selector(playbackStateDidChange:)
name:MPMoviePlayerPlaybackStateDidChangeNotification
object:controller];
[controller setContentURL:movieURL];
controller.view.frame = view.bounds;
[view addSubview: controller.view];
[controller play];
}
}
#end
// MoviePlayerView.h
#import <UIKit/UIKit.h>
#import "MoviePlayer.h"
#interface MoviePlayerView : MediaView
{
NSURL *movieURL;
NSURL *thumbnailURL;
UIImageView *previewImage;
UIView *iconView;
BOOL hasPreviewImage;
}
-(id) initWithFrame:(CGRect)frame thumbnailURL:(NSURL *)thumbnail movieURL:(NSURL *)movie;
-(void) loadStateDidChange:(NSNotification *)ntf;
-(void) playbackStateDidChange:(NSNotification *)ntf;
#end
// MoviePlayerView.m
#import "MoviePlayerView.h"
#interface MoviePlayerView()
-(void) initView;
-(void) initController;
-(void) playMovie;
-(void) setActivityIcon;
-(void) setMovieIcon:(float)alpha;
-(void) clearIcon;
-(CGPoint) centerPoint;
#end
#implementation MoviePlayerView
-(id) initWithFrame:(CGRect)frame thumbnailURL:(NSURL *)thumbnail movieURL:(NSURL *)movie
{
self = [super initWithFrame:frame];
if (self)
{
movieURL = [movie retain];
thumbnailURL = [thumbnail retain];
[self initView];
[self initController];
hasPreviewImage = NO;
loadingFinished = YES;
}
return self;
}
-(void) dealloc
{
[iconView release];
[previewImage release];
[movieURL release];
[super dealloc];
}
-(void)initView
{
self.backgroundColor = [UIColor blackColor];
// add preview image view and icon view
previewImage = [[UIImageView alloc] initWithFrame:self.bounds];
[previewImage setContentMode:UIViewContentModeScaleAspectFit];
UIImage *img = nil;
if (thumbnailURL)
{
img = [ImageUtils loadImageFromURL:thumbnailURL];
if (img)
{
previewImage.image = img;
hasPreviewImage = YES;
}
}
[self addSubview:previewImage];
[self setMovieIcon:(hasPreviewImage ? 0.8f : 0.3f)];
}
-(void)initController
{
UITapGestureRecognizer *rec = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(playMovie)];
[self addGestureRecognizer:rec];
[rec release];
}
-(void)playMovie
{
[[MoviePlayer instance] playMovie:movieURL onView:self];
[self setActivityIcon];
}
-(void) loadStateDidChange:(NSNotification *)ntf
{
MPMoviePlayerController *controller = [ntf object];
switch (controller.loadState)
{
case MPMovieLoadStatePlayable:
{
[self clearIcon];
[controller setFullscreen:YES animated:YES];
break;
}
case MPMovieLoadStateStalled:
{
[self setActivityIcon];
break;
}
default:
{
break; // nothing to be done
}
}
}
-(void) playbackStateDidChange:(NSNotification *)ntf
{
MPMoviePlayerController *controller = [ntf object];
switch (controller.playbackState)
{
case MPMoviePlaybackStatePlaying:
{
[self clearIcon];
break;
}
case MPMoviePlaybackStateStopped:
{
[self setMovieIcon:(hasPreviewImage ? 0.8f : 0.3f)];
break;
}
case MPMoviePlaybackStatePaused:
{
[self setMovieIcon:0.8f];
break;
}
case MPMoviePlaybackStateInterrupted:
{
[self setActivityIcon];
break;
}
default:
{
break; // nothing to be done
}
}
}
-(void) setActivityIcon
{
[self clearIcon];
iconView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
iconView.center = [self centerPoint];
[self addSubview:iconView];
[iconView performSelector:#selector(startAnimating)];
}
-(void) setMovieIcon:(float)alpha
{
[self clearIcon];
iconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"icon_movie.png"]];
iconView.center = [self centerPoint];
iconView.alpha = alpha;
[self addSubview:iconView];
}
-(void) clearIcon
{
if (iconView)
{
SEL stop = #selector(stopAnimating);
if ([iconView respondsToSelector:stop])
{
[iconView performSelector:stop];
}
[iconView removeFromSuperview];
[iconView release];
iconView = nil;
}
}
-(CGPoint) centerPoint
{
return CGPointMake(roundf(self.bounds.size.width / 2.0f), roundf(self.bounds.size.height / 2.0f));
}
-(void)resize
{
for (UIView *view in [self subviews])
{
if (view == iconView)
{
iconView.center = [self centerPoint];
continue;
}
view.frame = self.bounds;
}
[self addCaptionLabel];
}
-(void) layoutSubviews
{
[super layoutSubviews];
[self resize];
}
#end
...
player = [[MPMoviePlayerController alloc] initWithContentURL: [NSURL URLWithString:...
...
but I didn't gave internet connection to phone (wi-fi) :)
I had the same problem. My solution is using prepareToPlay:
MPMoviePlayerController *player = [[MPMoviePlayerController alloc] initWithContentOfURL:movieURL];
if (player) {
[player prepareToPlay];
//...
}
This error seems to be thrown for lots of different reasons, but the reason I found was that the MPMoviePlayerController class freaks out if you call methods in a certain order. From an IRC Channel:
"apparently if you call prepareToPlay WHILE setting source type and
NOT setting the view yet causes this crash"
So I fixed this by just making sure that I called prepareToPlay: LAST (or second to last, with the last being play:).
It is also weird because my original code worked in iOS 5.1, but this problem suddenly manifested when I started using the iOS 6.0 sdk. It is possibly a bug in the MPMoviePlayerController code, so I'm going to be filing a radar report on it, as calling prepareToPlay: before setting the view / setting the sourceFileType should not throw an exception (or at least an exception that seemingly has nothing to do with the actual error)