iOS】AVPlayer 播放音视频

这篇具有很好参考价值的文章主要介绍了iOS】AVPlayer 播放音视频。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1、常见的音视频播放器


iOS开发中不可避免地会遇到音视频播放方面的需求。

常用的音频播放器有 AVAudioPlayer、AVPlayer 等。不同的是,AVAudioPlayer 只支持本地音频的播放,而 AVPlayer 既支持本地音频播放,也支持网络音频播放。

常用的视频播放器有 MPMoviePlayerController、AVPlayer 等。不同的是,MPMoviePlayerController 内部做了高度封装,包含了播放控件,几乎不用写几行代码就能完成一个播放器,但是正是由于它的高度封装使得要自定义这个播放器变得很复杂,甚至是不可能完成。而 AVPlayer 更加接近于底层,所以灵活性也更强,更加方便自定义。

今天我们要介绍的主角就是强大的 AVPlayer。

2、AVPlayer


AVPlayer 存在于 AVFoundation 框架中,所以要使用 AVPlayer,要先在工程中导入 AVFoundation 框架。

AVPlayer 播放界面中不带播放控件,想要播放视频,必须要加入 AVPlayerLayer 中,并添加到其他能显示的 layer 当中。

AVPlayer 中音视频的播放、暂停功能对应着两个方法 play、pause 来实现。

大多播放器都是通过通知来获取播放器的播放状态、加载状态等,而 AVPlayer 中对于获得播放状态和加载状态有用的通知只有一个:AVPlayerItemDidPlayToEndTimeNotification(播放完成通知) 。播放器的播放状态判断可以通过播放器的播放速度 rate 来获得,如果 rate 为0说明是停止状态,为1时则是正常播放状态。想要获取视频播放情况、缓冲情况等的实时变化,可以通过 KVO 监控 AVPlayerItem 的 status、loadedTimeRanges 等属性来获得。当 AVPlayerItem 的 status 属性为 AVPlayerStatusReadyToPlay 时说明可以开始播放,只有处于这个状态时才能获得视频时长等信息;当 loadedTimeRanges 改变时(每缓冲一部分数据就会更新此属性),可以获得本次缓冲加载的视频范围(包含起始时间、本次加载时长),这样一来就可以实时获得缓冲情况。

AVPlayer 中播放进度的获取通常是通过:- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block 方法。这个方法会在设定的时间间隔内定时更新播放进度,通过 time 参数通知客户端。至于播放进度的跳转则是依靠 - (void)seekToTime:(CMTime)time 方法。

AVPlayer 还提供了 - (void)replaceCurrentItemWithPlayerItem:(AVPlayerItem *)item 方法用于在不同视频之间的切换(事实上在AVFoundation内部还有一个AVQueuePlayer专门处理播放列表切换,有兴趣的朋友可以自行研究,这里不再赘述)。

3、自定义AVPlayer


下面是我自己在项目中封装的音视频播放器,贴上代码,大家可以参考一下。

#import <UIKit/UIKit.h>

#import <Foundation/Foundation.h>

#import <AVFoundation/AVFoundation.h>

/**

播放器开始播放的通知

当存在多个播放器,可使用该通知在其他播放器播放时暂停当前播放器

*/

extern NSString * const YDPlayerDidStartPlayNotification;

/**

enum 播放器状态

- YDPlayerStatusUnknown: 未知

- YDPlayerStatusPlaying: 播放中

- YDPlayerStatusLoading: 加载中

- YDPlayerStatusPausing: 暂停中

- YDPlayerStatusFailed: 播放失败

- YDPlayerStatusFinished: 播放完成

*/

typedef NS_ENUM(NSInteger, YDPlayerStatus) {

YDPlayerStatusUnknown,

YDPlayerStatusPlaying,

YDPlayerStatusLoading,

YDPlayerStatusPausing,

YDPlayerStatusFailed,

YDPlayerStatusFinished

};

@interface YDPlayerMananger : NSObject

/**

播放器

*/

@property (nonatomic, strong) AVPlayer *player;

/**

播放器layer层

*/

@property (nonatomic, strong) AVPlayerLayer *playerLayer;

/**

当前PlayerItem

*/

@property (nonatomic, strong) AVPlayerItem *currentItem;

/**

播放器状态

*/

@property (nonatomic, assign) YDPlayerStatus playStatus;

/**

Item总时长回调

*/

@property (nonatomic, copy) void(^currentItemDurationCallBack)(AVPlayer *player, CGFloat duration);

/**

Item播放进度回调

*/

@property (nonatomic, copy) void(^currentPlayTimeCallBack)(AVPlayer *player, CGFloat time);

/**

Item缓冲进度回调

*/

@property (nonatomic, copy) void(^currentLoadedTimeCallBack)(AVPlayer *player, CGFloat time);

/**

Player状态改变回调

*/

@property (nonatomic, copy) void(^playStatusChangeCallBack)(AVPlayer *player, YDPlayerStatus status);

/**

初始化方法

@param url 播放链接

@return YDPlayerMananger对象

*/

- (instancetype)initWithURL:(NSURL *)url;

/**

创建单例对象

@return YDPlayerMananger单例对象

*/

+ (instancetype)shareManager;

/**

将播放器展示在某个View

@param view 展示播放器的View

*/

- (void)showPlayerInView:(UIView *)view withFrame:(CGRect)frame;

/**

替换PlayerItem

@param url 需要播放的链接

*/

- (void)replaceCurrentItemWithURL:(NSURL *)url;

/**

播放某个链接

@param urlStr 需要播放的链接

*/

- (void)playWithUrl:(NSString *)urlStr;

/**

开始播放

*/

- (void)play;

/**

暂停播放

*/

- (void)pause;

/**

停止播放

*/

- (void)stop;

/**

跳转到指定时间

@param time 指定的时间

*/

- (void)seekToTime:(CGFloat)time;

@end

#import "YDPlayerMananger.h"

NSString * const YDPlayerDidStartPlayNotification = @"YDPlayerDidStartPlayNotification";

@interface YDPlayerMananger ()

@property (nonatomic, strong) id timeObserver; // 监控播放进度的观察者

@end

@implementation YDPlayerMananger

#pragma mark - 生命周期

- (instancetype)init

{

if (self = [super init]) {

AVAudioSession *audioSession = [AVAudioSession sharedInstance];

[audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];

[audioSession setActive:YES error:nil];

self.player = [[AVPlayer alloc] init];

[self addNotificationAndObserver];

}

return self;

}

- (instancetype)initWithURL:(NSURL *)url

{

if (self = [self init]) {

[self replaceCurrentItemWithURL:url];

}

return self;

}

+ (instancetype)shareManager

{

static YDPlayerMananger *manager = nil;

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

manager = [[self alloc] init];

});

return manager;

}

- (void)dealloc

{

[self removeNotificationAndObserver];

}

#pragma mark - 公开方法

- (void)showPlayerInView:(UIView *)view withFrame:(CGRect)frame

{

self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];

_playerLayer.frame = frame;

_playerLayer.backgroundColor = [UIColor blackColor].CGColor;

_playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;

[view.layer addSublayer:_playerLayer];

}

- (void)replaceCurrentItemWithURL:(NSURL *)url

{

// 移除当前观察者

if (_currentItem) {

[_currentItem removeObserver:self forKeyPath:@"status"];

[_currentItem removeObserver:self forKeyPath:@"loadedTimeRanges"];

}

_currentItem = [[AVPlayerItem alloc] initWithURL:url];

[self.player replaceCurrentItemWithPlayerItem:_currentItem];

// 重新添加观察者

[_currentItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];

[_currentItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];

}

- (void)playWithUrl:(NSString *)urlStr

{

[self replaceCurrentItemWithURL:[NSURL URLWithString:urlStr]];

[self play];

}

- (void)play

{

[self.player play];

self.playStatus = YDPlayerStatusPlaying;

// 发起开始播放的通知

[[NSNotificationCenter defaultCenter] postNotificationName:YDPlayerDidStartPlayNotification object:_player];

}

- (void)pause

{

[self.player pause];

self.playStatus = YDPlayerStatusPausing;

}

- (void)stop

{

[self.player pause];

[_currentItem cancelPendingSeeks];

self.playStatus = YDPlayerStatusFinished;

}

- (void)seekToTime:(CGFloat)time

{

[_currentItem seekToTime:CMTimeMakeWithSeconds(time, 1.0) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];

}

#pragma mark - 私有方法

// 添加通知、观察者

- (void)addNotificationAndObserver

{

// 添加播放完成通知

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];

// 添加打断播放的通知

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(interruptionComing:) name:AVAudioSessionInterruptionNotification object:nil];

// 添加插拔耳机的通知

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(routeChanged:) name:AVAudioSessionRouteChangeNotification object:nil];

// 添加观察者监控播放器状态

[self addObserver:self forKeyPath:@"playStatus" options:NSKeyValueObservingOptionNew context:nil];

// 添加观察者监控进度

__weak typeof(self) weakSelf = self;

_timeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {

__strong typeof(self) strongSelf = weakSelf;

if (strongSelf.currentPlayTimeCallBack) {

float currentPlayTime = (double)strongSelf.currentItem.currentTime.value / strongSelf.currentItem.currentTime.timescale;

strongSelf.currentPlayTimeCallBack(strongSelf.player, currentPlayTime);

}

}];

}

// 移除通知、观察者

- (void)removeNotificationAndObserver

{

[[NSNotificationCenter defaultCenter] removeObserver:self];

[self removeObserver:self forKeyPath:@"playStatus"];

[_player removeTimeObserver:_timeObserver];

if (_currentItem) {

[_currentItem removeObserver:self forKeyPath:@"status"];

[_currentItem removeObserver:self forKeyPath:@"loadedTimeRanges"];

}

}

#pragma mark - 观察者

// 观察者

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

{

if ([keyPath isEqualToString:@"status"]) {

AVPlayerStatus status = [[change objectForKey:@"new"] intValue];

if (status == AVPlayerStatusReadyToPlay) {

// 获取视频长度

if (self.currentItemDurationCallBack) {

CGFloat duration = CMTimeGetSeconds(_currentItem.duration);

self.currentItemDurationCallBack(_player, duration);

}

} else if (status == AVPlayerStatusFailed) {

self.playStatus = YDPlayerStatusFailed;

} else {

self.playStatus = YDPlayerStatusUnknown;

}

} else if ([keyPath isEqualToString:@"playStatus"]) {

if (self.playStatusChangeCallBack) {

self.playStatusChangeCallBack(_player, _playStatus);

}

} else if ([keyPath isEqualToString:@"loadedTimeRanges"]) {

// 计算缓冲总进度

NSArray *loadedTimeRanges = [_currentItem loadedTimeRanges];

CMTimeRange timeRange = [loadedTimeRanges.firstObject CMTimeRangeValue];

float startSeconds = CMTimeGetSeconds(timeRange.start);

float durationSeconds = CMTimeGetSeconds(timeRange.duration);

NSTimeInterval loadedTime = startSeconds + durationSeconds;

if (self.playStatus == YDPlayerStatusPlaying && self.player.rate <= 0) {

self.playStatus = YDPlayerStatusLoading;

}

// 卡顿时缓冲完成后自动播放

if (self.playStatus == YDPlayerStatusLoading) {

NSTimeInterval currentTime = self.player.currentTime.value / self.player.currentTime.timescale;

if (loadedTime > currentTime + 5) {

[self play];

}

}

if (self.currentLoadedTimeCallBack) {

self.currentLoadedTimeCallBack(_player, loadedTime);

}

}

}

#pragma mark - 通知

// 播放完成通知

- (void)playbackFinished:(NSNotification *)notification

{

AVPlayerItem *playerItem = (AVPlayerItem *)notification.object;

if (playerItem == _currentItem) {

self.playStatus = YDPlayerStatusFinished;

}

}

// 插拔耳机通知

- (void)routeChanged:(NSNotification *)notification

{

NSDictionary *dic = notification.userInfo;

int changeReason = [dic[AVAudioSessionRouteChangeReasonKey] intValue];

// 旧输出不可用

if (changeReason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {

AVAudioSessionRouteDescription *routeDescription = dic[AVAudioSessionRouteChangePreviousRouteKey];

AVAudioSessionPortDescription *portDescription = [routeDescription.outputs firstObject];

// 原设备为耳机则暂停

if ([portDescription.portType isEqualToString:@"Headphones"]) {

[self pause];

}

}

}

// 来电、闹铃打断播放通知

- (void)interruptionComing:(NSNotification *)notification

{

NSDictionary *userInfo = notification.userInfo;

AVAudioSessionInterruptionType type = [userInfo[AVAudioSessionInterruptionTypeKey] intValue];

if (type == AVAudioSessionInterruptionTypeBegan) {

[self pause];

}

}

@end

4、注意点


在使用 AVPlayer 时需要注意的是,由于播放状态、缓冲状态等是通过 KVO 监控 AVPlayerItem 的 status、loadedTimeRanges 等属性来获得的,在使用 - (void)replaceCurrentItemWithPlayerItem:(AVPlayerItem *)item 切换视频后,当前的 AVPlayerItem 实际上已经被释放掉了,所以一定要及时移除观察者并重新添加,否则会引起崩溃。

如果有大神发现文章中的错误,欢迎指正。有兴趣下载文中 Demo 的朋友,可以前往我的GitHub:GitHud地址

原文链接:https://juejin.cn/post/68449041

★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。

见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓文章来源地址https://www.toymoban.com/news/detail-629584.html

到了这里,关于iOS】AVPlayer 播放音视频的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 作为一名iOS开发者—面对音视频这个新风口应该怎样学习才能乘风而起?

    作为一名iOS开发者—面对音视频这个新风口应该怎样学习才能乘风而起?

    5G时代,为何各大厂纷纷杀入音视频领域?这会是新的风口吗! 随着5G开始普及加上国内外网络资费的不断下降,音视频的前景已经越来越广阔! 大家都知道,在现在的日常生活中,视频类应用占据了我们越来越多的时间,不管是抖音、快手等短视频,还是斗鱼、虎牙这类的

    2024年02月01日
    浏览(14)
  • qt+ffmpeg 实现音视频播放(二)之音频播放

    qt+ffmpeg 实现音视频播放(二)之音频播放

    通过  avformat_open_input () 打开媒体文件并分配和初始化  AVFormatContext   结构体。 函数原型如下: int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options); 参数说明: - `ps`:指向 `AVFormatContext` 结构体指针的指针,用于存储打开的媒体文件的信息。

    2024年04月22日
    浏览(17)
  • 音视频项目—基于FFmpeg和SDL的音视频播放器解析(三)

    介绍 在本系列,我打算花大篇幅讲解我的 gitee 项目音视频播放器,在这个项目,您可以学到音视频解封装,解码,SDL渲染相关的知识。您对源代码感兴趣的话,请查看基于FFmpeg和SDL的音视频播放器 如果您不理解本文,可参考我的前一篇文章音视频项目—基于FFmpeg和SDL的音视

    2024年02月05日
    浏览(55)
  • 音视频项目—基于FFmpeg和SDL的音视频播放器解析(二十一)

    介绍 在本系列,我打算花大篇幅讲解我的 gitee 项目音视频播放器,在这个项目,您可以学到音视频解封装,解码,SDL渲染相关的知识。您对源代码感兴趣的话,请查看基于FFmpeg和SDL的音视频播放器 如果您不理解本文,可参考我的前一篇文章音视频项目—基于FFmpeg和SDL的音视

    2024年02月02日
    浏览(50)
  • FFMpeg-3、基于QT实现音视频播放显示

    FFMpeg-3、基于QT实现音视频播放显示

    1、音视频播放的基础知识 内容来自雷神博客 1、在Windows平台下的视频播放技术主要有以下三种:GDI,Direct3D和OpenGL;音频播放技术主要是DirectSound。 SDL本身并不具有播放显示的功能,它只是封装了底层播放显示的代码 记录三种视频显示技术:GDI,Direct3D,OpenGL。其中Direct3D包

    2024年02月03日
    浏览(16)
  • FFmpeg 播放器实现音视频同步的三种方式

    FFmpeg 播放器实现音视频同步的三种方式

    我们基于 FFmpeg 利用 OpenGL ES 和 OpenSL ES 分别实现了对解码后视频和音频的渲染,本文将实现播放器的最后一个重要功能:音视频同步。 老人们经常说, 播放器对音频和视频的播放没有绝对的静态的同步,只有相对的动态的同步,实际上音视频同步就是一个“你追我赶”的过

    2024年02月06日
    浏览(16)
  • iOS使用AVCaptureSession实现音视频采集

    AVCaptureSession配置采集行为并协调从输入设备到采集输出的数据流。要执行实时音视频采集,需要实例化采集会话并添加适当的输入和输出。 AVCaptureSession:管理输入输出音视频流 AVCaptureDevice:相机硬件的接口,用于控制硬件特性,诸如镜头的位置(前后摄像头)、曝光、闪光灯

    2024年02月06日
    浏览(12)
  • QtAV:基于Qt和FFmpeg的跨平台高性能音视频播放框架

    QtAV:基于Qt和FFmpeg的跨平台高性能音视频播放框架

    目录 一.简介 1.特性 2.支持的平台 3.简单易用的接口 二.编译 1.下载依赖包 2.开始编译 2.1克隆 2.2修改配置文件 2.3编译 三.试用 官网地址:http://www.qtav.org/ Github地址:https://github.com/wang-bin/QtAV ●支持大部分播放功能 ●播放、暂停、播放速度、快进快退、字幕、音量、声道、音

    2024年01月22日
    浏览(321)
  • WebRTC音视频通话-实现GPUImage视频美颜滤镜效果iOS

    WebRTC音视频通话-实现GPUImage视频美颜滤镜效果iOS

    WebRTC音视频通话-实现GPUImage视频美颜滤镜效果 在WebRTC音视频通话的GPUImage美颜效果图如下 可以看下 之前搭建ossrs服务,可以查看:https://blog.csdn.net/gloryFlow/article/details/132257196 之前实现iOS端调用ossrs音视频通话,可以查看:https://blog.csdn.net/gloryFlow/article/details/132262724 之前WebR

    2024年02月12日
    浏览(9)
  • iOS 端实现1对1音视频实时通话

    iOS 端实现1对1音视频实时通话

    首先,我们来看一下 iOS 端是如何获取访问音视频设备权限的。相比 Android 端而言,iOS端获取相关权限要容易很多。其步骤如下: 打开项目,点击左侧目录中的项目。 在左侧目录找到 info.plist ,并将其打开。 点击 右侧 看到 “+” 号的地方。 添加 Camera 和 Microphone 访问权限。

    2024年02月15日
    浏览(15)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包