IOS消息机制
NSNotification
类与类之间的通信(系统调用/解耦)
1.NSNotificationCenter
2.中心化管理
3.主要关注系统性事件而不是特有的对象
4.前后台切换 内存警告
使用Notification接收和处理播放状态通知
// 视频是否完成播放
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handlerPlayEnd) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
-(void)_handlerPlayEnd{
[_playerLayer removeFromSuperlayer];
_videoItem = nil;
_avplayer = nil;
}
使用KVO监听和处理视频播放资源的状态变化
-(void)_tapToPlay{
// NSURL *videoUrl = [NSURL URLWithString:_videoUrl];
NSString *path = [[NSBundle mainBundle] pathForResource:_videoUrl ofType:nil];
NSURL *videoUrl = [NSURL fileURLWithPath:path]; //播放本地视频
AVAsset *asset = [AVAsset assetWithURL:videoUrl];
_videoItem = [AVPlayerItem playerItemWithAsset:asset];
[_videoItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil]; //监听状态
_avplayer = [AVPlayer playerWithPlayerItem:_videoItem];
_playerLayer = [AVPlayerLayer playerLayerWithPlayer:_avplayer];
_playerLayer.frame = _coverView.bounds;
[_coverView.layer addSublayer:_playerLayer];
}
#pragma mark - KVO
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if([keyPath isEqualToString:@"status"]){
if(((NSNumber *)[change objectForKey:NSKeyValueChangeNewKey]).integerValue == AVPlayerItemStatusReadyToPlay){
[_avplayer play];
}
else{
//
}
}
}
监听视频播放的缓冲与进度
AVPlayer播放进度
·CMTimer;
·CMTimerMake(第几帧,帧率)
·duration/currentTime
·播放进度回调
-(void)_tapToPlay{
// NSURL *videoUrl = [NSURL URLWithString:_videoUrl];
NSString *path = [[NSBundle mainBundle] pathForResource:_videoUrl ofType:nil];
NSURL *videoUrl = [NSURL fileURLWithPath:path]; //播放本地视频
AVAsset *asset = [AVAsset assetWithURL:videoUrl];
_videoItem = [AVPlayerItem playerItemWithAsset:asset];
[_videoItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
[_videoItem addObserver:self forKeyPath:@"loadTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
CMTime videoDuration = _videoItem.duration;
CGFloat duration = CMTimeGetSeconds(videoDuration);
_avplayer = [AVPlayer playerWithPlayerItem:_videoItem];
_playerLayer = [AVPlayerLayer playerLayerWithPlayer:_avplayer];
_playerLayer.frame = _coverView.bounds;
[_coverView.layer addSublayer:_playerLayer];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if([keyPath isEqualToString:@"status"]){
if(((NSNumber *)[change objectForKey:NSKeyValueChangeNewKey]).integerValue == AVPlayerItemStatusReadyToPlay){
[_avplayer play];
}
else if([keyPath isEqualToString:@"loadTimeRanges"]){
NSLog(@"缓存:%@", [change objectForKey:NSKeyValueChangeNewKey]);
}
}
}
使用单例模式重构播放器业务逻辑
创建简单的音视频播放器
·使用KVO监听播放状态
·使用Notification接收通知
·视频播放器的生命周期管理
·一般来说App内只能同时播放一个视频
·建议用单例进行管理
·内部处理播放器的销毁和播放状态切换
//
// GSCVideoPlayer.h
// GSCApp1
//
// Created by gsc on 2024/6/5.
//
#import <Foundation/Foundation.h>
#import "UIKit/UIKit.h"
NS_ASSUME_NONNULL_BEGIN
@interface GSCVideoPlayer : NSObject
+(GSCVideoPlayer *)Player;
-(void)playWithVideoCoverUrl:(NSString *)videoUrl attachView:(UIView *)attachView;
@end
NS_ASSUME_NONNULL_END
//
// GSCVideoPlayer.m
// GSCApp1
//
// Created by gsc on 2024/6/5.
//
#import "GSCVideoPlayer.h"
#import "AVFoundation/AVFoundation.h"
@interface GSCVideoPlayer()
@property(nonatomic, strong, readwrite) AVPlayer *avplayer;
@property(nonatomic, strong, readwrite) AVPlayerLayer *playerLayer;
@property(nonatomic, strong, readwrite) AVPlayerItem *videoItem;
@end
@implementation GSCVideoPlayer
+(GSCVideoPlayer *)Player{
static GSCVideoPlayer *player;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
player = [[GSCVideoPlayer alloc] init];
});
return player;
}
-(void)playWithVideoCoverUrl:(NSString *)videoUrl attachView:(UIView *)attachView{
[self _stopPlay];
// NSURL *videoUrl = [NSURL URLWithString:_videoUrl];
NSString *path = [[NSBundle mainBundle] pathForResource:videoUrl ofType:nil];
NSURL *videoLocalUrl = [NSURL fileURLWithPath:path]; //播放本地视频
AVAsset *asset = [AVAsset assetWithURL:videoLocalUrl];
_videoItem = [AVPlayerItem playerItemWithAsset:asset];
[_videoItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
[_videoItem addObserver:self forKeyPath:@"loadTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
CMTime videoDuration = _videoItem.duration;
CGFloat duration = CMTimeGetSeconds(videoDuration);
_avplayer = [AVPlayer playerWithPlayerItem:_videoItem];
[_avplayer addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
NSLog(@"播放进度%@",@(CMTimeGetSeconds(time)));
}];
_playerLayer = [AVPlayerLayer playerLayerWithPlayer:_avplayer];
_playerLayer.frame = attachView.bounds;
[attachView.layer addSublayer:_playerLayer];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handlerPlayEnd) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
NSLog(@"");
}
#pragma mark - private method
-(void)_stopPlay{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_playerLayer removeFromSuperlayer];
[_videoItem removeObserver:self forKeyPath:@"status"];
[_videoItem removeObserver:self forKeyPath:@"loadTimeRanges"];
_videoItem = nil;
_avplayer = nil;
}
-(void)_handlerPlayEnd{
[_avplayer seekToTime:CMTimeMake(0, 1)];
[_avplayer play];
}
#pragma mark - KVO
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if([keyPath isEqualToString:@"status"]){
if(((NSNumber *)[change objectForKey:NSKeyValueChangeNewKey]).integerValue == AVPlayerItemStatusReadyToPlay){
[_avplayer play];
}
else if([keyPath isEqualToString:@"loadTimeRanges"]){
NSLog(@"缓存:%@", [change objectForKey:NSKeyValueChangeNewKey]);
}
}
}
@end