I've been playing around with this for a day now and have hit a brick wall.
I want to have a UIScrollView that shows a series of videos that the user can scroll across. Setting up the UIScrollView is fine and each item within it (I'm calling them MenuItems) is a subclass of UiViewController that contains and manages all of the setup of the MPMoviePlayerController etc.
I discovered however that you can only have one MPMoviePlayerController in a window, playing at once.
So, I thought the best way to handle this is to have accessible methods on each MenuItem, stopVideo and startVideo that I would trigger as each menu item became the "focus" of the UiScrollView (I have successfully coded the delegate so that it captures the scroll event and works out which page is in the centre of the scrollview).
The problem is, I can't work out how to access the MenuItem objects in the UiScrollView.
I have the following code in the UIScrollView delegate to do this:
- (void)scrollViewDidScroll:(UIScrollView *)sView {
//establish what page we're on
static NSInteger previousPage = 0;
MenuItem *currentMenuItem;
MenuItem *previousMenuItem;
CGFloat pageWidth = sView.frame.size.width;
float fractionalPage = sView.contentOffset.x / pageWidth;
NSInteger page = lround(fractionalPage);
if (previousPage != page) {
//firstly, get the previous page and stop the video
previousMenuItem = [sView.subviews objectAtIndex:previousPage];
previousMenuItem = [previousMenuItem nextResponder];
[previousMenuItem hideVideo];
//[previousMenuItem release];
//page has changed, get the new current page and start the video
currentMenuItem = [sView.subviews objectAtIndex:page];
currentMenuItem = [currentMenuItem nextResponder];
[currentMenuItem showVideo];
//[currentMenuItem release];
previousPage = page;
}
}
The methods showVideo and hideVideo are called, but I get other errors (for example, I can't seem to initialize an MPMoviePlayerController inside the MenuItem without an SIGABRT error).
I'm thinking my methodology is flawed here or that there is a simpler approach? All help appreciated!
Many thanks.
bit late for a response but I got stuck on this as well and managed to bash out a solution so thought I'd include it for anyone else.
I think it has to do with memory allocation for the movie players.
I included this code in my .h file
#interface VideoInstructionViewController : UIViewController <UIScrollViewDelegate>
{
MPMoviePlayerController *player0;
MPMoviePlayerController *player1;
MPMoviePlayerController *player2;
}
and so that the movie players were effectively global and then just included this code in my .m file.
player0 = [[MPMoviePlayerController alloc] initWithContentURL:contentURL];
player1 = [[MPMoviePlayerController alloc] initWithContentURL:contentURL];
player2 = [[MPMoviePlayerController alloc] initWithContentURL:contentURL];
The rest of your code was very useful, I think it's a solid methodology, thanks!
Related
This has got to be the dumbest question of the day, but I'm just not getting it.
I create a Quicklook, which shows just fine. When I hit the Done button, it just reappears. How do I intercept the Done button? Or more generally, control what is displayed in what I assume is a navbar. Here is the relevant code:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
/*
* get the path to the pdf resource.
*/
NSString *path = [[NSBundle mainBundle] pathForResource:#"article" ofType:#"pdf"];
NSURL *docURL = [NSURL fileURLWithPath:path];
/*
* create the Quicklook controller.
*/
QLPreviewController *qlController = [[QLPreviewController alloc] init];
PreviewItem *item = [[PreviewItem alloc] initPreviewURL:docURL WithTitle:#"Article"];
self.pdfDatasource = [[PDFDataSource alloc] initWithPreviewItem:item];
qlController.dataSource = self.pdfDatasource;
/*
* present the document.
*/
[self presentViewController:qlController animated:YES completion:nil];
}
I assume I am missing something obvious.
Thank you,
Ken
Did you tried Taking your ViewDidAppear code To ViewDidLoad ? As when you click on done button all the views of the Controller are being Loaded again except ViewDidLoad. So the quicklook view Appears again. Just Try
The trick was to roll everything back into the original viewcontroller. That way when I hit the done button it goes back to the original viewcontroller, which is exactly what I wanted. so instead of having a separate class, I just incorporated the calls right into my main viewcontroller. I suspect that there is still a way to do with a cunning use of delegates, but in case anyone else is having the same issue, this was a solution that worked for me.
Thank you for your attention and help.
Ken
I am working on the livestream feature for an iOS application. I have used an AVPlayerViewController contained within a ContainerView to display the video.
And here is the associated code for the ViewController
#interface ViewController ()
#property MPMoviePlayerController* streamPlayer;
#property BOOL isPlaying;
#end
AVPlayerViewController *streamPlayer;
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.isPlaying = NO;
NSURL *streamURL = [NSURL URLWithString:#"http://vevoplaylist-live.hls.adaptive.level3.net/vevo/ch1/appleman.m3u8"];
streamPlayer = [[ UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"StreamPlayer" ];
streamPlayer.player = [ AVPlayer playerWithURL:streamURL ];
}
- (IBAction)playVideo:(id)sender{
if ( !self.isPlaying ){
[ streamPlayer.player play ];
[self.button setTitle:#"Stop" forState:UIControlStateNormal ];
self.isPlaying = YES;
} else {
[ streamPlayer.player pause ];
[self.button setTitle:#"Play" forState:UIControlStateNormal ];
self.isPlaying = NO;
}
}
(You can ignore the isPlaying variable. It's just to see what I can control)
The stream starts and works fine for a few minutes before it stops/buffers(i think) for a few seconds and an error show up:
ERROR: 849: AudioQueue: request to trim 4291961269 + 0 = 4291961269 frames from buffer containing 21504 frames
The stream continues after this but there is no audio.
Occasionally there is a second or two of audio but immediately the error showing up again and the stream continues in silence. (Something I'm glad of when a Pitbull video is being broadcast. )
If I pause the stream and then play it, the audio returns for a few minutes and then goes off again.
I have searched for this error but have not found anything to help me fix this. Being relatively new to iOS and HLS, I don't have any ideas as to what the issue could be but if I had to guess I'd say that it's an issue with synchronization of audio and video.
Any and all help is appreciated.
There are two issues in your code that stand out right away.
You have TWO streamPlayer variables.
The AVPlayerViewController variable should be a Global Variable
By putting your AVPlayerViewController property declaration inside your Interface Extension as a global variable the error should be resolved.
The code would look something like this:
#interface ViewController ()
#property (nonatomic) MPMoviePlayerController *mpStreamPlayer;
#property (nonatomic) AVPlayerViewController *avStreamPlayer;
#property (nonatomic) BOOL isPlaying;
#end
#implementation ViewController
- (void)viewDidLoad ... // continue as expected
The reason I am assuming it will be resolved is because of this similar problem a member of the community is experiencing with the Error 849 (their codebase is SWIFT) and the answer working answer.
this is possibly related to the iOS 9 bugs, paste your m3u8 into the Safari, you could experience the same problem down the road (intermediate audio interrupt, audio/video out of sync), there is nothing you can do with your code.
see some report here, and Apple engineer ask to file a bug report.
I have an Objective-C app with a programmatically created view for which I'm seeing strange (to me) behavior. Specifically, there's a long pause (up to 30 seconds) between the end of the init method and the beginning of the loadView method. I'm unable to see anything out of the ordinary when setting breakpoints at both those places, so I'm wondering what the system is doing between the init and the loadView, and whether there's anything I can do to optimize the work being done at that point.
Thanks.
Edit: adding some code below (abbreviated for clarity)
The app plays and records video. This view controller reviews a video that's just been recorded but not yet saved / posted.
// called from the view controller that pushes this onto the navigation controller stack
- (id)initWithURL:(NSURL *)fileURL
{
self = [super init];
if (self) {
self.localFileURL = fileURL;
sessionManager = [[sessionManager alloc] initWithURL:fileURL];
}
NSLog(#"return self");
return self;
}
- (void)loadView
{
NSLog(#"begin loadView");
self.playView = [[PlayView alloc] initWithFrame:screenRect];
self.view = self.playView;
}
The pause occurs between the two logging statements.
I'm trying to write a simple plugin for Unity3d iOS which you might have heard about that streams video. I actually managed to do that, the streaming video bit works.
Now i'm trying to add functionality that is part of the plugin to detect for a swipe gesture and send a message to Unity. I have no experience with Objective C, and currently not interested in learning the ins and outs as i'm only looking to find a solution for this one specific problem.
So i have managed to Google all the stuff required to stream the actual video, and also some code for registering swipe gestures. The problem is though that, in defining the UISwipeGestureRecognizer you are expected to assign an action method to it. But the function doing the video streaming is being defined in an extern "C" block which is required so that it can be referenced in Unity.
The method assigned to the gesture recognizer though has to be defined (i think) in the regular framework of an iOS app, but my suspicion is that this creates the problem of the gesture recognizer class being unaware of the method defined outside the extern "C" block.
So now when i run it, the video starts streaming but as soon i start swiping the screen it all crashes. Presumably because the method assigned cannot be referenced is my guess.
My question is... How do i make this happen, maybe there's something obvious that i'm unaware of ? The important thing is to make it work in a function that is defined in an extern "C" block, because that after all is required by Unity.
This is the actual code i put together so far:
http://www.hastebin.com/ragocorola.m <-- complete code
To surmise the loadLevel method how should it be declared ?
extern "C" {
void _playVideo(const char *videoFilepath)
{
NSURL *url = [NSURL URLWithString:CreateNSString(videoFilepath)];
MPMoviePlayerController *player = [[MPMoviePlayerController alloc]
initWithContentURL:url];
player.controlStyle = MPMovieControlStyleFullscreen;
player.view.transform = CGAffineTransformConcat(player.view.transform,
CGAffineTransformMakeRotation(M_PI_2));
UIWindow *backgroundWindow = [[UIApplication sharedApplication] keyWindow];
[player.view setFrame:backgroundWindow.frame];
[backgroundWindow addSubview:player.view];
UISwipeGestureRecognizer * swipe = [[UISwipeGestureRecognizer alloc]
initWithTarget:swipe action:#selector(loadLevel:)];
[swipe setDirection:(UISwipeGestureRecognizerDirectionUp |
UISwipeGestureRecognizerDirectionDown | UISwipeGestureRecognizerDirectionLeft
|UISwipeGestureRecognizerDirectionRight)];
[player.view addGestureRecognizer:swipe];
[player play];
}
}
Your problem is that swipe is undefined when you are passing it as a target. Who know's what's on the stack when it's passed? This results in a method being sent to a bad location in memory when you swipe.
UISwipeGestureRecognizer * swipe = [[UISwipeGestureRecognizer alloc]
initWithTarget:swipe action:#selector(loadLevel:)];
this is equivalent to:
id undefinedTarget;
UISwipeGestureRecognizer * swipe = [[UISwipeGestureRecognizer alloc]
initWithTarget:undefinedTarget action:#selector(loadLevel:)];
Your target needs to be an instance of a class that defines the loadLevel: method.
Edit (after chasing your link): i.e. an instance of VideoPlugin.
Though the second problem you will have is that method loadLevel: is different from loadLevel. Make sure they are consistent.
So I'm using ARC in my project and when I add an AVPlayerLayer it works just fine and dandy, but when I pop the UIViewController from my UINavigationItem the video continues to play in the background. Does anyone know how you would handle this? It seems like something easy I'm just overlooking. Here's the code I have for the initially instantiations.
self.currentItem = [[AVPlayerItem alloc] initWithURL:url];
self.player = [[AVPlayer alloc]initWithPlayerItem:self.currentItem];
self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
self.avPlayerLayer.bounds = self.view.bounds;
self.avPlayerLayer.frame = CGRectMake(0,55, 1024, 670);
self.view.backgroundColor = [UIColor clearColor];
[self.view.layer addSublayer:avPlayerLayer];
Also this is how I have the properties definied.
#property (strong) AVPlayer *player;
#property (strong) AVPlayerLayer *avPlayerLayer;
#property (strong) AVPlayerItem *currentItem;
Maybe that's entirely wrong as well. I'm not exactly sure when to use (strong) vs (weak). Any case thank you ahead of time for any help.
If avPlayerLayer is the only class interacting with the avPlayer, you don't need to maintain a reference to it with a property in the class you're using to present it (unless this class uses it outside the code you've shared). In fact, this is probably why it isn't working the way you expect.
The reason the clip continues to play (I think) is because the player isn't being deallocated. You create it, own it with the strong property in your class, than it's owned again by the AVPlayerLayer class you hand it to. So when the AVPlayerLayer is deallocated, the AVPlayer looses one owner. But it still has an owner (your class), so it isn't deallocated, and it keeps playing. The solution here is to get rid of your owning property altogether for *avPlayer. You don't need it. Create the AVPlayer and pass it to AVPlayerLayer. That should be all that's needed.
Something else you could do which might fix the behavior but not the problem, call:
[avPlayer pause]
In your AVPlayerLayer's dealloc method.
Re: Strong vs. Weak references: A strong reference implies ownership. As ARC is managing memory, it'll be doing all the [object retain]ing and [object release]ing that you previously would have done in code OR that you would have done with properties, ie:
#property (retain) NSObject *iAmRetainedWhenProperyIsAssigned;
So now with ARC, we simple users don't use words like retain or release in code or when defining properties. But we still design software. ARC is smart, but not smart enough to infer the architectural implications of relationships that we're defining. It still needs to be told that a class "owns" a reference to the object that the property refers to. Which reduces us, in the most basic terms:
(strong) means to ARC what (retain) meant to properties pre-ARC (owned/retained)
(weak) means to ARC what (assign) meant to properites pre-ARC (not owned/not retained)
Maybe you did not pause. Do it and then remove layer and nullify player.
[player Pause];
[player removefromsuperlayer];
player = nil;
When you want to stop an AVPlayerItem from loading, use AVQueuePlayer's removeAllItems and then re-initialize it.
[self.avPlayer removeAllItems];
self.avPlayer = [AVQueuePlayer playerWithURL:[NSURL URLWithString:#""]];
self.avPlayer = nil;
This will stop the current item from loading -- it is the only way I found to accomplish this.
2023 answer ...
As there are only very old answers. These days in fact:
( 1 ) All you need to do is nil it in the playerLayer:
///Unplay and return to blankness.
public func unplay() {
playerLayer.player = nil
}
However, as #isaac astutely pointed out, you almost always also have just a variable with the player in it (to make it easier for you to control it). Don't forget that you of course have to nil that as well.
Thus when you lunch the player, your code will look something like
var handyPlayer: AVPlayer?
...
handyPlayer = AVPlayer(url: u)
playerLayer.player = handyPlayer
handyPlayer?.play()
handyPlayer? ... seek, etc etc
( 2 ) Thus, almost always, the code is:
///Unplay and return to blankness.
public func unplay() {
playerLayer.player = nil
handyPlayer = nil
}
There is no need to pause, release anything, or anything else. That's all there is to it.