I've noticed that AVPlayerItemDidPlayToEndTimeNotification is occasionally sent when the network is deactivated and the video cannot continue playing. This is despite the video not having completed.
This can be reproduced with the following code:
#import "ViewController.h"
#import
#interface ViewController ()
#property AVPlayerItem *item;
#property AVPlayer *player;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *url = [NSURL URLWithString:#"https://video-dev.github.io/streams/x36xhzz/x36xhzz.m3u8"];
self.item = [[AVPlayerItem alloc] initWithURL: url];
self.player = [[AVPlayer alloc] initWithPlayerItem:self.item];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
playerLayer.frame = self.view.bounds;
[NSNotificationCenter.defaultCenter addObserver:self selector:#selector(handleNotification:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.item];
[self.view.layer addSublayer:playerLayer];
[self.player addObserver:self forKeyPath:#"status" options:0 context:nil];
[[AVAudioSession sharedInstance]
setCategory: AVAudioSessionCategoryPlayback
error: nil];
[self.player play];
NSLog(#"Playing");
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
if (object == self.player && [keyPath isEqualToString:#"status"]) {
}
}
-(void)handleNotification:(NSNotification*)notification {
NSLog(#"%#", notification.name.debugDescription);
NSLog(#"HandleNotification called at:%lld seconds. Duration on player is:%lld seconds", self.player.currentTime.value/self.player.currentTime.timescale, self.item.duration.value/self.item.duration.timescale);
}
#end
Reproduction steps are:
1. Run the app and let the video play for around 40s.
2. Kill the network on the device.
AVPlayerItemDidPlayToEndTimeNotification is fired and written to the log.
Is there any reason why I should expect this to happen? And if so, how can I distinguish between an end of play event and a failure to continue playback due to lack of buffered content?
I found same issue from AVPlayer's notification.
When AVPlayerItemFailedToPlayToEndTime is trigged, I check AVPlayerItem's position from the end.
Code sample from ModernAVPlayer library:
Related
I have an ESP8266-CAM which I connect to my WiFi and stream it out. I have it so I can go onto any browser anywhere and type in the address http://xxx.xxxxx.xxxx.xx.xx/xxxxx/x and it will play. I have it working in a Blynk app also using this address. I would like to include it in an iPhone app I am building.
I have the following code to play the stream.
#import "ViewController.h"
#import <AVKit/AVKit.h>
#import <AVFoundation/AVFoundation.h>
#interface ViewController ()
#property (nonatomic, strong) AVPlayer *player;
#property (nonatomic,strong) AVPlayerItem * playerItem;
#property (nonatomic, strong )AVPlayerLayer *avPlayerLayer;
-(IBAction)Turnon:(id)sender;
- (void)viewDidLoad {
[super viewDidLoad];
}
-(void) startplayer
{
NSURL *url = [[NSURL alloc]initWithString:#"xxxxxxxxxxx/xxxxx/1"];
AVPlayerItem * playerItem= [AVPlayerItem playerItemWithURL:url];
self.player = [AVPlayer playerWithPlayerItem:playerItem];
self.player.allowsExternalPlayback= YES;
self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
self.avPlayerLayer.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
//self.player.automaticallyWaitsToMinimizeStalling = false;
self.avPlayerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
//I HAVE TRIED THIS AT DIFFERENT LAYERS
[self.view.layer insertSublayer:self.avPlayerLayer atIndex:0
];
//THIS DOES PRINT IN LOG
NSLog(#"this is getting played");
[self.player.currentItem addObserver:self
forKeyPath:#"status"
options:0
context:nil];
}
-(IBAction)showVideo:(id)sender;{
[self startplayer];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (object == self.player.currentItem && [keyPath isEqualToString:#"status"])
{
NSLog(#"This is being called");
[self.player play];
self.player.rate = 1.0;
if (self.player.currentItem.status == AVPlayerStatusReadyToPlay)
{
//THIS IS ALSO BEING PRINTED IN LOG
NSLog(#"This is being called");
[self.player play];
self.player.rate = 1.0;
}
}
}
#end
Even though when I press the button to showVideo and the log show all parts are activated, no screen shows up. Does anyone have any ideas why this is happening?
I have figured out that the camera is sending out a steam of mjpeg images that work in the video player in Blynk but apparently Apple doesn't use. I think this is the problem in that the player can't recognize or decode the format so no image nothing.
Does anyone have an idea of how to play an mjpeg image streams
So I couldn't get the video to stream using AVPlayer, but by creating a WKWebView and opening the server in it I got the live video to play. Only thing is getting it to resize bigger one can see the details better. But at least it works. Here is the code.
#import <WebKit/WKWebViewConfiguration.h>
WKWebViewConfiguration *theConfiguration = [[WKWebViewConfiguration alloc] init];
self.webView = [[WKWebView alloc] initWithFrame:CGRectMake(self.view.center.x-(320/2),self.view.center.y-(240/2), 320, 240) configuration:theConfiguration];
NSURL *nsurl=[NSURL URLWithString:#"http://70.176.81.136:81/CFC878/1"];
NSURLRequest *nsrequest=[NSURLRequest requestWithURL:nsurl];
[_webView loadRequest:nsrequest];
[self.view insertSubview:self.webView atIndex:14];
Like the title says, does anyone know the reason?
Note that it does not happen on iOS 11.
My debug environment:
Xcode 9.2
iOS 9, 10, 11
My code:
In my app, I am trying to play a streaming content by AVFoundation framework. Showing the video's length and current playing time is also a feature.
The loadVideo method is called in an IBAction due to a tapped button.
Also, no special code in observeValueForKeyPath method to receive the event of KVO.
The details is listed as following:
- (void)loadVideo
{
AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:VIDEO_URL]];
Float64 duration = CMTimeGetSeconds(item.asset.duration);
[self updateTimeLabel:0.0 duration:duration];
self.player = [[AVPlayer alloc] initWithPlayerItem:item];
AVPlayerLayer *layer = (AVPlayerLayer *)self.playerView.layer;
[layer setPlayer:self.player];
[self.player addObserver:self forKeyPath:#"status" options:NSKeyValueObservingOptionNew context:nil];
__weak ViewController *weakSelf = self;
self.token = [self.player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, NSEC_PER_SEC)
queue:nil
usingBlock:^(CMTime time) {
Float64 currentTime = CMTimeGetSeconds(time);
[weakSelf updateTimeLabel:currentTime duration:duration];
}];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if ([keyPath isEqualToString:#"status"]) {
AVPlayer *player = (AVPlayer *)object;
switch (player.status) {
case AVPlayerStatusReadyToPlay:
{
NSLog(#"player's status changed to AVPlayerStatusReadyToPlay");
}
break;
default:
NSLog(#"player's status changed");
break;
}
[player removeObserver:self forKeyPath:#"status"];
}
}
Remote content doesn't work like local content.
In iOS 9, you should be using the AVPlayerItem as the locus of information about the arrival and playback of your AVAsset from across the network, keeping track of properties such as playbackLikelyToKeepUp and the accessLog, along with notifications such as AVPlayerItemPlaybackStalled.
In iOS 10 and later, you can use the AVPlayer, but the thing to watch is its timeControlStatus.
You should observe AVPlayerItem, not AVPlayer.
[self.item addObserver:self forKeyPath:#"status" options:NSKeyValueObservingOptionNew context:nil];
Apple's documentation
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
I'm playing youtube videos using AVPlayer as follows,
- (void)startYoutubeVideoAtUrl:(NSURL *)videoUrl
{
NSLog(#"start player at url : %#", videoUrl);
[HCYoutubeParser h264videosWithYoutubeURL:videoUrl completeBlock:^(NSDictionary *videoDictionary, NSError *error) {
if (videoDictionary && videoDictionary.count > 0) {
NSString *URLString = [self chooseYoutubeUrlFromUrlList:videoDictionary];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:[self videoURLWithCustomScheme:#"streaming" uRLString:URLString] options:nil];
[asset.resourceLoader setDelegate:self queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
pendingRequests = [NSMutableArray array];
avPlayerItem = [AVPlayerItem playerItemWithAsset:asset];
[self startVideoPlayBack];
}
else {
[_delegate failedStartPalyInlineVideo];
}
}];
}
-(void)startVideoPlayBack
{
startTime = CFAbsoluteTimeGetCurrent();
avPlayer = [[AVQueuePlayer alloc] initWithPlayerItem:avPlayerItem];
avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:avPlayer];
[avPlayerItem addObserver:self forKeyPath:#"status" options:0 context:nil];
[avPlayerLayer addObserver:self forKeyPath:#"readyForDisplay" options:NSKeyValueObservingOptionNew context:nil];
avPlayerLayer.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
[self.view.layer addSublayer:avPlayerLayer];
[self watchApiCall];
avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[avPlayer currentItem]];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemPlaybackStalled:)
name:AVPlayerItemPlaybackStalledNotification
object:[avPlayer currentItem]];
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
#try {
if (!avPlayer) {
return;
}
if (avPlayerItem.status == AVPlayerStatusReadyToPlay) {
}
else if (avPlayerItem.status == AVPlayerStatusFailed) {
NSLog(#"----- AVPlayerStatusFailed ----");
[self playbackVideo];
}
if (object == avPlayerLayer && [keyPath isEqualToString:#"readyForDisplay"]) {
[self resetNetworkSpeedUsingLoadingTime];
if (avPlayerLayer.readyForDisplay) {
id<LoopingVideoDelegate> strongDelegate = self.delegate;
if([strongDelegate readyForVideoDisplay]) {
[avPlayer play];
}
else {
[self removePlayer];
}
}
}
}
#catch(NSException *ex) {
NSLog(#"EXCEPTION : %#", ex);
}
}
My issue is some times the video getting stalled and fires AVPlayerItemPlaybackStalledNotification. Also it's not responding [avPlayerItem seekToTime:kCMTimeZero]; sometimes after called AVPlayerItemDidPlayToEndTimeNotification selector. I wasn't able to find a solution for this. I checked with,
[HCYoutubeParser thumbnailForYoutubeURL:videoUrl thumbnailSize:YouTubeThumbnailDefaultHighQuality completeBlock:^(UIImage *image, NSError *error) {
if (!error) {
[HCYoutubeParser h264videosWithYoutubeURL:videoUrl completeBlock:^(NSDictionary *videoDictionary, NSError *error) {
NSString *URLString = [self chooseYoutubeUrlFromUrlList:videoDictionary];
NSURL *urlToLoad = [NSURL URLWithString:URLString];
avPlayerItem = [[AVPlayerItem alloc] initWithURL:urlToLoad];
[self startVideoPlayBack];
}];
}
else {
NSLog(#"error in youtube parser");
}
}];
and there is no any player stall issue or seekToTime: not responding issue with that. Please help.
Observe your AVPlayerItem's loadedTimeRanges and seekableTimeRanges property to make make sure that the AVPlayer has loaded playable data yet. When steaming, the AVPlayer often pause falling short of playable(data that can be played by AVPlayer) data. This should help investigate further into the issue. You can also try to start playback when you get avPlayerItem.status == AVPlayerStatusReadyToPlay by calling play on your AVPlayer
From AV Foundation Programming Guide
Monitoring Playback
You can monitor a number of aspects of both the presentation state of a player and the player item being played. This is particularly useful for state changes that are not under your direct control. For example:
If the user uses multitasking to switch to a different application, a player’s rate property will drop to 0.0.
If you are playing remote media, a player item’s loadedTimeRanges and seekableTimeRanges properties will change as more data becomes available.
These properties tell you what portions of the player item’s timeline are available.
A player’s currentItem property changes as a player item is created for an HTTP live stream.
A player item’s tracks property may change while playing an HTTP live stream.
This may happen if the stream offers different encodings for the content; the tracks change if the player switches to a different encoding.
A player or player item’s status property may change if playback fails for some reason.
You can use key-value observing to monitor changes to values of these properties.
I want to monitor downloaded bytes when streaming mp3 from an URL and print it out to an IULabel. I can't find any easy method to do that.
What about "AVPlayerItemAccessLog", does it have the information? I can't figure out how to use that? Does anybody know how to get such information?
Below is my code for play the stream:
-(void)play
{
/*Allow radio to run in background*/
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive: YES error: nil];
/*Gets the url fra sender*/
NSString *urlString= self.radioStation.url;
NSURL *url = [NSURL URLWithString:urlString];
/*Stop the radio if its play or have info*/
if(self.avPlayerItem)
{
[self stop];
}
/*Sets the Avplayeritem*/
self.avPlayerItem = [AVPlayerItem playerItemWithURL:url];
/*Listen for changes in the avPlayerItem*/
[self.avPlayerItem addObserver:self forKeyPath:#"status" options:0 context:nil];
/*Sets the avplayer with avplayeritem*/
self.avPlayer = [AVPlayer playerWithPlayerItem:self.avPlayerItem];
/*Sends loading info to the nortification*/
[[NSNotificationCenter defaultCenter] postNotificationName:#"Loading" object:self];
/*Plays the radio*/
[self.avPlayer play];
}
You need to use KVO on the AVPlayerItem to get this information. Something like this:
[self.player.currentItem addObserver:self
forKeyPath:#"loadedTimeRanges"
options:NSKeyValueObservingOptionNew
context:NULL];
And then the standard observer method:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
// Get the player item's loadedTimeRanges value here.
}
Don't forget to remove the observers before releasing the player item.