KSVideoPlayerView.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. //
  2. // KSVideoPlayerView.m
  3. // KulexiuSchoolStudent
  4. //
  5. // Created by 王智 on 2023/9/5.
  6. //
  7. #import "KSVideoPlayerView.h"
  8. #import <AVFoundation/AVFoundation.h>
  9. #import <CloudAccompanyLibrary/AVPlayer+KSSeekSmoothly.h>
  10. @interface KSVideoPlayerView ()
  11. @property (nonatomic, strong) AVPlayer *videoPlayer;
  12. @property(nonatomic,strong) AVPlayerItem *currentItem;
  13. @property (nonatomic,retain) AVPlayerLayer *playerLayer;
  14. @property(nonatomic,retain) id timeObserver;//时间观察
  15. @property (nonatomic, assign) BOOL cacheFinish;
  16. @property (nonatomic, assign) BOOL hasFreeObserver;
  17. @property (nonatomic, assign) BOOL needResume; // 是否需要恢复播放
  18. @end
  19. @implementation KSVideoPlayerView
  20. -(void)layoutSubviews{
  21. [super layoutSubviews];
  22. self.playerLayer.frame = self.bounds;
  23. }
  24. - (instancetype)init {
  25. self = [super init];
  26. if (self) {
  27. self.backgroundColor = [UIColor blackColor];
  28. }
  29. return self;
  30. }
  31. - (void)initPlayer {
  32. [UIApplication sharedApplication].idleTimerDisabled=YES;
  33. }
  34. - (void)resetPlayer {
  35. if (self.playerLayer) {
  36. [self.playerLayer removeFromSuperlayer];
  37. self.playerLayer = nil;
  38. }
  39. [self.videoPlayer replaceCurrentItemWithPlayerItem:nil];
  40. self.videoPlayer = nil;
  41. }
  42. - (void)preparePlayNativeVideoWithPath:(NSURL *)urlStr {
  43. self.cacheFinish = NO;
  44. AVURLAsset *asset = [AVURLAsset URLAssetWithURL:urlStr options:nil];
  45. if (_currentItem) {
  46. [self removeAllNoticeAndObserver];
  47. [self resetPlayer];
  48. if (self.playerLayer) {
  49. [self.playerLayer removeFromSuperlayer];
  50. }
  51. }
  52. _currentItem = [AVPlayerItem playerItemWithAsset:asset];
  53. _videoPlayer = [AVPlayer playerWithPlayerItem:_currentItem];
  54. if ([self.videoPlayer respondsToSelector:@selector(automaticallyWaitsToMinimizeStalling)]) {
  55. self.videoPlayer.automaticallyWaitsToMinimizeStalling = YES;
  56. }
  57. self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.videoPlayer];
  58. self.playerLayer.frame = self.frame;
  59. [self.layer insertSublayer:self.playerLayer atIndex:0];
  60. //添加监听
  61. [self addNotificationAndObserver];
  62. self.volume = 1.0;
  63. self.isMute = NO;
  64. }
  65. - (void)addNotificationAndObserver {
  66. if (self.currentItem) {
  67. self.hasFreeObserver = NO;
  68. //添加播放器状态的监听
  69. [self addAVPlayerStatusObserver];
  70. //添加数据缓存的监听
  71. [self addNetDataStatusObserver];
  72. //添加时间的监听
  73. [self addTimeObserve];
  74. //添加播放完成的通知
  75. [self addPlayToEndObserver];
  76. }
  77. }
  78. - (void)startPlay {
  79. if (_isPlaying) {
  80. [_videoPlayer pause];
  81. }
  82. _isPlaying = YES;
  83. @weakObj(self);
  84. CMTime toleranceTime = kCMTimeZero;
  85. [_videoPlayer ss_seekToTime:kCMTimeZero toleranceBefore:toleranceTime toleranceAfter:toleranceTime completionHandler:^(BOOL finished) {
  86. @strongObj(self);
  87. [self.videoPlayer play];
  88. }];
  89. }
  90. - (void)resumePlay {
  91. if (_isPlaying) {
  92. [_videoPlayer pause];
  93. }
  94. _isPlaying = YES;
  95. [self.videoPlayer play];
  96. }
  97. - (void)puasePlay {
  98. self.needResume = NO;
  99. if (_isPlaying) {
  100. _isPlaying = NO;
  101. [_videoPlayer pause];
  102. }
  103. }
  104. - (void)freePlayer {
  105. if (_isPlaying) {
  106. _isPlaying = NO;
  107. [_videoPlayer pause];
  108. }
  109. [self removeAllNoticeAndObserver];
  110. [self resetPlayer];
  111. }
  112. - (void)seekToStart {
  113. CMTime toleranceTime = CMTimeMake(1, 1000);
  114. [_videoPlayer ss_seekToTime:kCMTimeZero toleranceBefore:toleranceTime toleranceAfter:toleranceTime completionHandler:^(BOOL finished) {
  115. }];
  116. }
  117. // 从某个位置开始播放 ms
  118. - (void)seekToTimePlay:(NSInteger)time {
  119. if (_isPlaying) {
  120. [_videoPlayer pause];
  121. }
  122. _isPlaying = YES;
  123. CMTime offsetCTTime = CMTimeMake(labs(time), 1000);
  124. CMTime toleranceTime = CMTimeMake(1, 1000);
  125. @weakObj(self);
  126. [_videoPlayer ss_seekToTime:offsetCTTime toleranceBefore:toleranceTime toleranceAfter:toleranceTime completionHandler:^(BOOL finished) {
  127. @strongObj(self);
  128. [self.videoPlayer play];
  129. }];
  130. }
  131. - (void)seekOffsetTime:(NSInteger)offsetTime {
  132. CMTime newTime = CMTimeMake(offsetTime, 1000);
  133. CMTime toleranceTime = CMTimeMake(1, 1000);
  134. if (self.isPlaying) {
  135. self.needResume = YES;
  136. }
  137. [self.videoPlayer pause];
  138. @weakObj(self);
  139. [self.videoPlayer ss_seekToTime:newTime toleranceBefore:toleranceTime toleranceAfter:toleranceTime completionHandler:^(BOOL finished) {
  140. @strongObj(self);
  141. if (self.needResume) {
  142. [self.videoPlayer play];
  143. }
  144. self.needResume = NO;
  145. }];
  146. }
  147. - (CMTime)getCurrentPlayTime {
  148. return [self.videoPlayer currentTime];
  149. }
  150. - (void)setIsMute:(BOOL)isMute {
  151. _isMute = isMute;
  152. [self.videoPlayer setMuted:isMute];
  153. }
  154. #pragma mark----设置player的volume
  155. - (void)setVolume:(CGFloat)volume {
  156. _volume = volume;
  157. _videoPlayer.volume=_volume;
  158. }
  159. -(void)addTimeObserve
  160. {
  161. __block typeof(self) bself = self;
  162. _timeObserver = [_videoPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1, 100) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
  163. //设置player的声音
  164. // [bself setPlayerVolume];
  165. //当前时间
  166. float current = CMTimeGetSeconds(time);
  167. //总共时间
  168. float total = CMTimeGetSeconds(bself.videoPlayer.currentItem.asset.duration);
  169. //进度
  170. float progress = current/total;
  171. //将值传入知道delegate方法中
  172. // NSLog(@"--current %f -- total %f -----%f", current, total, progress);
  173. if (bself.delegate && [bself.delegate respondsToSelector:@selector(getSongCurrentTime:andTotalTime:andProgress:currentInterval:playTime:inPlayer:)]) {
  174. NSDate *date = [NSDate date];
  175. NSTimeInterval inteveral = [date timeIntervalSince1970];
  176. // NSLog(@"----- start play %f", inteveral * 1000);
  177. [bself.delegate getSongCurrentTime:current*1000 andTotalTime:total*1000 andProgress:progress currentInterval:inteveral playTime:current*1000 inPlayer:bself.videoPlayer];
  178. }
  179. }];
  180. }
  181. #pragma mark---移除时间观察者
  182. -(void)removeTimeObserver
  183. {
  184. if (_timeObserver) {
  185. [_videoPlayer removeTimeObserver:_timeObserver];
  186. _timeObserver = nil;
  187. }
  188. }
  189. #pragma mark---float转00:00类型
  190. - (NSString *)formatTime:(float)num{
  191. int sec =(int)num%60;
  192. int min =(int)num/60;
  193. if (num < 60) {
  194. return [NSString stringWithFormat:@"00:%02d",(int)num];
  195. }
  196. return [NSString stringWithFormat:@"%02d:%02d",min,sec];
  197. }
  198. #pragma mark----监听播放器的加载状态
  199. -(void)addAVPlayerStatusObserver
  200. {
  201. [_currentItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
  202. }
  203. #pragma mark----数据缓冲状态的监听
  204. -(void)addNetDataStatusObserver
  205. {
  206. [_currentItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
  207. }
  208. #pragma mark----KVO方法
  209. -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
  210. {
  211. //播放器缓冲状态
  212. if ([keyPath isEqualToString:@"status"]) {
  213. switch (_videoPlayer.status) {
  214. case AVPlayerStatusUnknown:{
  215. _isPlaying = NO;
  216. NSLog(@"未知状态,此时不能播放");
  217. }
  218. break;
  219. case AVPlayerStatusReadyToPlay:{
  220. NSLog(@"准备完毕,可以播放");
  221. dispatch_main_sync_safe(^{
  222. self.isReady = YES;
  223. if (self.delegate && [self.delegate respondsToSelector:@selector(videoPlayerIsReadyPlay:)]) {
  224. [self.delegate videoPlayerIsReadyPlay:self.videoPlayer];
  225. }
  226. });
  227. }
  228. break;
  229. case AVPlayerStatusFailed:{
  230. _isPlaying = NO;
  231. NSLog(@"加载失败,网络或者服务器出现问题");
  232. if (self.delegate && [self.delegate respondsToSelector:@selector(videoPlayerOccurError:)]) {
  233. [self.delegate videoPlayerOccurError:self.videoPlayer];
  234. }
  235. }
  236. break;
  237. default:
  238. break;
  239. }
  240. }
  241. //数据缓冲状态
  242. if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
  243. // 计算缓冲进度
  244. NSTimeInterval timeInterval = [self availableDuration];
  245. CGFloat totalDuration = CMTimeGetSeconds(self.videoPlayer.currentItem.asset.duration);
  246. if (timeInterval == totalDuration && self.cacheFinish == NO) {
  247. self.cacheFinish = YES;
  248. dispatch_main_sync_safe(^{
  249. if (self.delegate && [self.delegate respondsToSelector:@selector(preparePlay:)]) {
  250. [self.delegate preparePlay:self.videoPlayer];
  251. }
  252. });
  253. }
  254. }
  255. }
  256. #pragma mark - 计算缓冲进度
  257. /**
  258. * 计算缓冲进度
  259. *
  260. * @return 缓冲进度
  261. */
  262. - (NSTimeInterval)availableDuration {
  263. NSArray *loadedTimeRanges = [[_videoPlayer currentItem] loadedTimeRanges];
  264. CMTimeRange timeRange = [loadedTimeRanges.firstObject CMTimeRangeValue];// 获取缓冲区域
  265. CGFloat startSeconds = CMTimeGetSeconds(timeRange.start);
  266. CGFloat durationSeconds = CMTimeGetSeconds(timeRange.duration);
  267. CGFloat result = startSeconds + durationSeconds;// 计算缓冲总进度
  268. return result;
  269. }
  270. #pragma mark---移除媒体加载状态的监听
  271. -(void)removeAVPlayerObserver
  272. {
  273. [_currentItem removeObserver:self forKeyPath:@"status"];
  274. }
  275. #pragma mark---移除数据加载状态的监听
  276. -(void)removeNetDataObserver
  277. {
  278. [_currentItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
  279. }
  280. #pragma mark----播放完成后发送通知
  281. -(void)addPlayToEndObserver
  282. {
  283. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:_currentItem];
  284. }
  285. #pragma mark---通知的方法
  286. -(void)playFinished:(NSNotification *)notice
  287. {
  288. //移除所有监听
  289. _isPlaying = NO;
  290. if ([self.delegate respondsToSelector:@selector(videoPlayerFinishedPlay:)]) {
  291. [self.delegate videoPlayerFinishedPlay:self.videoPlayer];
  292. }
  293. }
  294. #pragma mark----移除通知
  295. - (void)removePlayToEndNotice
  296. {
  297. [[NSNotificationCenter defaultCenter] removeObserver:self];
  298. }
  299. #pragma mark-----移除所有监听
  300. -(void)removeAllNoticeAndObserver
  301. {
  302. if (self.hasFreeObserver) {
  303. return;
  304. }
  305. self.hasFreeObserver = YES;
  306. //移除时间进度的监听
  307. [self removeTimeObserver];
  308. //移除播放完成的通知
  309. [self removePlayToEndNotice];
  310. //移除播放器状态的监听
  311. [self removeAVPlayerObserver];
  312. //移除数据缓存的监听
  313. [self removeNetDataObserver];
  314. }
  315. - (void)dealloc {
  316. NSLog(@" -------- video player dealloc ");
  317. }
  318. /*
  319. // Only override drawRect: if you perform custom drawing.
  320. // An empty implementation adversely affects performance during animation.
  321. - (void)drawRect:(CGRect)rect {
  322. // Drawing code
  323. }
  324. */
  325. @end