iAm*_*Amd 39 audio avfoundation ios avplayer
在我的应用程序中,我必须播放存储在Web服务器上的音频文件.我正在使用AVPlayer它.我有所有播放/暂停控件和所有代表和观察员在那里工作得非常好.在播放小音频文件时,一切都很棒.
播放长音频文件时,它也会开始正常播放,但几秒后AVPlayer暂停播放(最可能是缓冲它).问题是它不能再自行恢复.它保持暂停状态,如果我再次手动按下播放按钮,它将再次流畅播放.
我想知道为什么AVPlayer不自动恢复,如何在没有用户再次按下播放按钮的情况下再次恢复音频?谢谢.
Jpe*_*lat 17
是的,它会因为缓冲区为空而停止,因此必须等待加载更多视频.之后,您必须手动要求重新开始.为了解决这个问题,我遵循了以下步骤:
1)检测:要检测播放器何时停止,我使用具有该值的rate属性的KVO:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:@"rate"] )
{
if (self.player.rate == 0 && CMTimeGetSeconds(self.playerItem.duration) != CMTimeGetSeconds(self.playerItem.currentTime) && self.videoPlaying)
{
[self continuePlaying];
}
}
}
Run Code Online (Sandbox Code Playgroud)
这个条件:CMTimeGetSeconds(self.playerItem.duration) != CMTimeGetSeconds(self.playerItem.currentTime)是检测到达视频结尾或中间停止之间的差异
2)等待视频加载 - 如果继续直接播放,则没有足够的缓冲区继续播放而不会中断.要知道何时开始,你必须观察playbackLikelytoKeepUp来自playerItem 的值(这里我使用一个库来观察块,但我认为这是重点):
-(void)continuePlaying
{
if (!self.playerItem.playbackLikelyToKeepUp)
{
self.loadingView.hidden = NO;
__weak typeof(self) wSelf = self;
self.playbackLikelyToKeepUpKVOToken = [self.playerItem addObserverForKeyPath:@keypath(_playerItem.playbackLikelyToKeepUp) block:^(id obj, NSDictionary *change) {
__strong typeof(self) sSelf = wSelf;
if(sSelf)
{
if (sSelf.playerItem.playbackLikelyToKeepUp)
{
[sSelf.playerItem removeObserverForKeyPath:@keypath(_playerItem.playbackLikelyToKeepUp) token:self.playbackLikelyToKeepUpKVOToken];
sSelf.playbackLikelyToKeepUpKVOToken = nil;
[sSelf continuePlaying];
}
}
}];
}
Run Code Online (Sandbox Code Playgroud)
就是这样!问题解决了
编辑:顺便说一下,库使用的是libextobjc
我正在使用视频文件,因此我的代码比您需要的更多,但是以下解决方案应该在播放器挂起时暂停播放,然后每0.5秒检查一次,看看我们是否已经足够缓冲以便跟上.如果是这样,它会重新启动播放器.如果玩家在没有重新启动的情况下挂起超过10秒钟,我们会停止播放器并向用户道歉.这意味着您需要合适的观察员.下面的代码对我来说非常好.
在.h文件或其他地方定义/初始化的属性:
AVPlayer *player;
int playerTryCount = -1; // this should get set to 0 when the AVPlayer starts playing
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
Run Code Online (Sandbox Code Playgroud)
部分.m:
- (AVPlayer *)initializePlayerFromURL:(NSURL *)movieURL {
// create AVPlayer
AVPlayerItem *videoItem = [AVPlayerItem playerItemWithURL:movieURL];
AVPlayer *videoPlayer = [AVPlayer playerWithPlayerItem:videoItem];
// add Observers
[videoItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:nil];
[self startNotificationObservers]; // see method below
// I observe a bunch of other stuff, but this is all you need for this to work
return videoPlayer;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
// check that all conditions for a stuck player have been met
if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) {
if (self.player.currentItem.playbackLikelyToKeepUp == NO &&
CMTIME_COMPARE_INLINE(self.player.currentTime, >, kCMTimeZero) &&
CMTIME_COMPARE_INLINE(self.player.currentTime, !=, self.player.currentItem.duration)) {
// if so, post the playerHanging notification
[self.notificationCenter postNotificationName:PlayerHangingNotification object:self.videoPlayer];
}
}
}
- (void)startNotificationObservers {
[self.notificationCenter addObserver:self
selector:@selector(playerContinue)
name:PlayerContinueNotification
object:nil];
[self.notificationCenter addObserver:self
selector:@selector(playerHanging)
name:PlayerHangingNotification
object:nil];
}
// playerHanging simply decides whether to wait 0.5 seconds or not
// if so, it pauses the player and sends a playerContinue notification
// if not, it puts us out of our misery
- (void)playerHanging {
if (playerTryCount <= 10) {
playerTryCount += 1;
[self.player pause];
// start an activity indicator / busy view
[self.notificationCenter postNotificationName:PlayerContinueNotification object:self.player];
} else { // this code shouldn't actually execute, but I include it as dummyproofing
[self stopPlaying]; // a method where I clean up the AVPlayer,
// which is already paused
// Here's where I'd put up an alertController or alertView
// to say we're sorry but we just can't go on like this anymore
}
}
// playerContinue does the actual waiting and restarting
- (void)playerContinue {
if (CMTIME_COMPARE_INLINE(self.player.currentTime, ==, self.player.currentItem.duration)) { // we've reached the end
[self stopPlaying];
} else if (playerTryCount > 10) // stop trying
[self stopPlaying];
// put up "sorry" alert
} else if (playerTryCount == 0) {
return; // protects against a race condition
} else if (self.player.currentItem.playbackLikelyToKeepUp == YES) {
// Here I stop/remove the activity indicator I put up in playerHanging
playerTryCount = 0;
[self.player play]; // continue from where we left off
} else { // still hanging, not at end
// create a 0.5-second delay to see if buffering catches up
// then post another playerContinue notification to call this method again
// in a manner that attempts to avoid any recursion or threading nightmares
playerTryCount += 1;
double delayInSeconds = 0.5;
dispatch_time_t executeTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(executeTime, dispatch_get_main_queue(), ^{
// test playerTryCount again to protect against changes that might have happened during the 0.5 second delay
if (playerTryCount > 0) {
if (playerTryCount <= 10) {
[self.notificationCenter postNotificationName:PlayerContinueNotification object:self.videoPlayer];
} else {
[self stopPlaying];
// put up "sorry" alert
}
}
});
}
Run Code Online (Sandbox Code Playgroud)
希望能帮助到你!
我有类似的问题.我有一些我想播放的本地文件,配置AVPlayer并调用[播放器播放],播放器停在第0帧并且不再播放,直到我再次手动调用播放.由于错误的解释,我不可能实现接受的答案,然后我只是尝试推迟播放并神奇地工作
[self performSelector:@selector(startVideo) withObject:nil afterDelay:0.2];
-(void)startVideo{
[self.videoPlayer play];
}
Run Code Online (Sandbox Code Playgroud)
对于网络视频我也有问题,我用华莱士的答案解决它.
创建AVPlayer时添加一个观察者:
[self.videoItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
// check that all conditions for a stuck player have been met
if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) {
if (self.videoPlayer.currentItem.playbackLikelyToKeepUp == NO &&
CMTIME_COMPARE_INLINE(self.videoPlayer.currentTime, >, kCMTimeZero) &&
CMTIME_COMPARE_INLINE(self.videoPlayer.currentTime, !=, self.videoPlayer.currentItem.duration)) {
NSLog(@"hanged");
[self performSelector:@selector(startVideo) withObject:nil afterDelay:0.2];
}
}
Run Code Online (Sandbox Code Playgroud)
}
请记住在解除视图之前删除观察者
[self.videoItem removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"]
Run Code Online (Sandbox Code Playgroud)
接受的答案给出了问题的可能解决方案,但它缺乏灵活性,也难以阅读.这是更灵活的解决方案.
添加观察员:
//_player is instance of AVPlayer
[_player.currentItem addObserver:self forKeyPath:@"status" options:0 context:nil];
[_player addObserver:self forKeyPath:@"rate" options:0 context:nil];
Run Code Online (Sandbox Code Playgroud)
处理器:
-(void)observeValueForKeyPath:(NSString*)keyPath
ofObject:(id)object
change:(NSDictionary*)change
context:(void*)context {
if ([keyPath isEqualToString:@"status"]) {
if (_player.status == AVPlayerStatusFailed) {
//Possibly show error message or attempt replay from tart
//Description from the docs:
// Indicates that the player can no longer play AVPlayerItem instances because of an error. The error is described by
// the value of the player's error property.
}
}else if ([keyPath isEqualToString:@"rate"]) {
if (_player.rate == 0 && //if player rate dropped to 0
CMTIME_COMPARE_INLINE(_player.currentItem.currentTime, >, kCMTimeZero) && //if video was started
CMTIME_COMPARE_INLINE(_player.currentItem.currentTime, <, _player.currentItem.duration) && //but not yet finished
_isPlaying) { //instance variable to handle overall state (changed to YES when user triggers playback)
[self handleStalled];
}
}
}
Run Code Online (Sandbox Code Playgroud)
魔法:
-(void)handleStalled {
NSLog(@"Handle stalled. Available: %lf", [self availableDuration]);
if (_player.currentItem.playbackLikelyToKeepUp || //
[self availableDuration] - CMTimeGetSeconds(_player.currentItem.currentTime) > 10.0) {
[_player play];
} else {
[self performSelector:@selector(handleStalled) withObject:nil afterDelay:0.5]; //try again
}
}
Run Code Online (Sandbox Code Playgroud)
"[self availableDuration]"是可选的,但您可以根据可用的视频量手动启动播放.您可以更改代码检查是否有足够的视频缓冲的频率.如果您决定使用可选部分,这里是方法实现:
- (NSTimeInterval) availableDuration
{
NSArray *loadedTimeRanges = [[_player currentItem] loadedTimeRanges];
CMTimeRange timeRange = [[loadedTimeRanges objectAtIndex:0] CMTimeRangeValue];
Float64 startSeconds = CMTimeGetSeconds(timeRange.start);
Float64 durationSeconds = CMTimeGetSeconds(timeRange.duration);
NSTimeInterval result = startSeconds + durationSeconds;
return result;
}
Run Code Online (Sandbox Code Playgroud)
不要忘记清理.删除观察员:
[_player.currentItem removeObserver:self forKeyPath:@"status"];
[_player removeObserver:self forKeyPath:@"rate"];
Run Code Online (Sandbox Code Playgroud)
可能有待处理停止视频的待处理呼叫:
[UIView cancelPreviousPerformRequestsWithTarget:self selector:@selector(handleStalled) object:nil];
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
26116 次 |
| 最近记录: |