iOS开发基础63-远程音频与视频播放:AVPlayer、AVPlayerLayer、AVPlayerViewController 与 MediaPlayer
iOS 远程音频与视频播放深度解析:AVPlayer、AVPlayerLayer、AVPlayerViewController 与 MediaPlayer
本文系统梳理 iOS 远程音频与视频播放:AVPlayer 播放远程流媒体(音频/视频)的完整配置(KVO 状态监听、进度监听、缓冲进度、播放完成、后台播放、ATS)、AVPlayerLayer 视频渲染与填充模式、AVPlayerViewController(现代推荐,自带播放控制 UI)、以及 MediaPlayer 框架(MPMoviePlayerController/MPMoviePlayerViewController,已废弃)的历史用法与迁移。
一、远程音频播放(AVPlayer)
1. AVPlayer 简介
AVAudioPlayer 只支持本地文件,不支持远程流媒体。播放远程音频(网络 URL)需要使用 AVPlayer(AVFoundation 框架)。
AVPlayer 既可以播放音频,也可以播放视频,支持本地和远程 URL,是 iOS 流媒体播放的核心类。
2. 基本使用
#import <AVFoundation/AVFoundation.h>
@interface ViewController ()
@property (nonatomic, strong) AVPlayer *player;
@property (nonatomic, strong) AVPlayerItem *currentItem;
@end
@implementation ViewController
- (AVPlayer *)player {
if (!_player) {
// 远程音频 URL(建议 HTTPS,HTTP 需配置 ATS 例外)
NSURL *url = [NSURL URLWithString:@"https://example.com/audio.m4a"];
// 方式一:直接用 URL 创建
// _player = [[AVPlayer alloc] initWithURL:url];
// 方式二:用 AVPlayerItem 包装(推荐,便于监听状态和切换曲目)
AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:url];
_player = [[AVPlayer alloc] initWithPlayerItem:item];
self.currentItem = item;
}
return _player;
}
// 播放
- (IBAction)play {
[self.player play];
}
// 暂停
- (IBAction)pause {
[self.player pause];
}
// 更换音频
- (void)replaceAudioWithURL:(NSURL *)url {
AVPlayerItem *newItem = [[AVPlayerItem alloc] initWithURL:url];
[self.player replaceCurrentItemWithPlayerItem:newItem];
self.currentItem = newItem;
}
@end
ATS(App Transport Security):iOS 9+ 默认禁止 HTTP 明文请求,远程 URL 应使用 HTTPS。如果必须用 HTTP,需在 Info.plist 中配置
NSAppTransportSecurity→NSAllowsArbitraryLoads= YES(或针对特定域名配置例外)。
3. 配置后台播放
远程音频通常需要支持后台播放,配置方式与本地音乐相同:
- Info.plist 添加
UIBackgroundModes=audio。 - 设置 AVAudioSession 类别为 Playback:
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
[session setActive:YES error:nil];
4. 播放状态监听(KVO)
AVPlayer 的状态通过 KVO 监听,不能直接读取(初始化时 status 为 AVPlayerStatusUnknown):
// 监听 AVPlayer 的 status
[self.player addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
// 监听当前播放项的 status
[self.currentItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
// 监听缓冲进度(loadedTimeRanges)
[self.currentItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
// 监听播放速率变化(timeControlStatus,iOS 10+)
[self.player addObserver:self forKeyPath:@"timeControlStatus" options:NSKeyValueObservingOptionNew context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:@"status"]) {
if (object == self.player) {
// AVPlayer status
if (self.player.status == AVPlayerStatusReadyToPlay) {
NSLog(@"准备播放,可以调用 play");
} else if (self.player.status == AVPlayerStatusFailed) {
NSLog(@"播放失败: %@", self.player.error);
}
} else if (object == self.currentItem) {
// AVPlayerItem status
if (self.currentItem.status == AVPlayerItemStatusReadyToPlay) {
NSLog(@"当前曲目准备就绪");
}
}
} else if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
// 缓冲进度
NSArray *ranges = self.currentItem.loadedTimeRanges;
if (ranges.count > 0) {
CMTimeRange timeRange = [ranges.firstObject CMTimeRangeValue];
NSTimeInterval bufferTime = CMTimeGetSeconds(timeRange.start) + CMTimeGetSeconds(timeRange.duration);
NSTimeInterval duration = CMTimeGetSeconds(self.currentItem.duration);
NSLog(@"缓冲进度: %.0f%%", bufferTime / duration * 100);
}
} else if ([keyPath isEqualToString:@"timeControlStatus"]) {
// iOS 10+:播放/暂停/等待状态
switch (self.player.timeControlStatus) {
case AVPlayerTimeControlStatusPlaying:
NSLog(@"正在播放");
break;
case AVPlayerTimeControlStatusPaused:
NSLog(@"已暂停");
break;
case AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate:
NSLog(@"等待缓冲中...");
break;
}
}
}
- (void)dealloc {
[self.player removeObserver:self forKeyPath:@"status"];
[self.currentItem removeObserver:self forKeyPath:@"status"];
[self.currentItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
[self.player removeObserver:self forKeyPath:@"timeControlStatus"];
}
5. 播放进度监听
// 定期监听播放进度(每秒回调一次)
__weak typeof(self) weakSelf = self;
self.timeObserver = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1)
queue:dispatch_get_main_queue()
usingBlock:^(CMTime time) {
NSTimeInterval current = CMTimeGetSeconds(time);
NSTimeInterval duration = CMTimeGetSeconds(weakSelf.currentItem.duration);
NSLog(@"进度: %.0f / %.0f 秒", current, duration);
// 更新进度条
// weakSelf.progressView.progress = current / duration;
}];
// 移除进度监听
- (void)dealloc {
if (self.timeObserver) {
[self.player removeTimeObserver:self.timeObserver];
}
}
6. 播放完成通知
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playerItemDidPlayToEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:self.currentItem];
- (void)playerItemDidPlayToEnd:(NSNotification *)note {
NSLog(@"播放完成,可以播放下一首或循环");
// 循环播放:
// [self.player seekToTime:kCMTimeZero completionHandler:^(BOOL finished) {
// [self.player play];
// }];
}
7. 跳转播放位置
// 跳转到指定时间(秒)
- (void)seekToTime:(NSTimeInterval)seconds {
CMTime time = CMTimeMakeWithSeconds(seconds, NSEC_PER_SEC);
[self.player seekToTime:time completionHandler:^(BOOL finished) {
NSLog(@"跳转完成");
}];
}
二、视频播放(AVPlayer + AVPlayerLayer)
1. 基本使用
AVPlayer 播放视频时,需要用 AVPlayerLayer 将视频画面渲染到屏幕上:
#import <AVFoundation/AVFoundation.h>
@interface ViewController ()
@property (nonatomic, strong) AVPlayer *player;
@property (nonatomic, strong) AVPlayerLayer *playerLayer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 1. 创建 URL(远程或本地)
NSURL *url = [NSURL URLWithString:@"https://example.com/video.mp4"];
// 本地视频:
// NSURL *url = [[NSBundle mainBundle] URLForResource:@"video" withExtension:@"mp4"];
// 2. 创建 AVPlayer
AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:url];
self.player = [[AVPlayer alloc] initWithPlayerItem:item];
// 3. 创建 AVPlayerLayer(渲染视频画面)
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
[self.view.layer addSublayer:self.playerLayer];
}
// frame 应在 viewDidLayoutSubviews 中设置(约束布局完成后)
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
// 16:9 视频比例
CGFloat width = self.view.bounds.size.width;
self.playerLayer.frame = CGRectMake(0, 100, width, width * 9 / 16);
}
- (IBAction)play {
[self.player play];
}
@end
布局时机:不要在
viewDidLoad中用self.view.bounds设置 AVPlayerLayer 的 frame,此时约束还未完成布局,尺寸可能不准确。应在viewDidLayoutSubviews中设置。
2. 视频填充模式(videoGravity)
AVPlayerLayer 有三种视频填充模式:
| videoGravity | 效果 | 说明 |
|---|---|---|
AVLayerVideoGravityResizeAspect |
保持比例,完整显示,留黑边 | 默认值 |
AVLayerVideoGravityResizeAspectFill |
保持比例,填满区域,裁剪超出部分 | 类似 UIImageView 的 AspectFill |
AVLayerVideoGravityResize |
拉伸填满,不保持比例,可能变形 | 类似 UIImageView 的 ScaleToFill |
self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
三、AVPlayerViewController(现代推荐)
1. 简介
手动用 AVPlayer + AVPlayerLayer 实现视频播放需要自己写播放控制 UI(进度条、播放/暂停、全屏、AirPlay 等),工作量大。
iOS 8+ 苹果提供了 AVPlayerViewController(AVKit 框架),它自带完整的播放控制 UI,是现代视频播放的推荐方案,替代已废弃的 MPMoviePlayerViewController。
2. 基本使用
#import <AVKit/AVKit.h>
#import <AVFoundation/AVFoundation.h>
@interface ViewController ()
@property (nonatomic, strong) AVPlayerViewController *playerVC;
@end
@implementation ViewController
- (void)playVideo {
NSURL *url = [NSURL URLWithString:@"https://example.com/video.mp4"];
AVPlayer *player = [[AVPlayer alloc] initWithURL:url];
// 创建 AVPlayerViewController
self.playerVC = [[AVPlayerViewController alloc] init];
self.playerVC.player = player;
// 设置是否显示播放控制(默认 YES)
self.playerVC.showsPlaybackControls = YES;
// 是否允许画中画(iPad,iOS 9+)
self.playerVC.allowsPictureInPicturePlayback = YES;
// 进入全屏时是否隐藏状态栏
self.playerVC.updatesNowPlayingInfoCenter = YES;
// modal 弹出(全屏播放)
[self presentViewController:self.playerVC animated:YES completion:^{
[player play];
}];
// 或 push 到导航控制器(非全屏)
// [self.navigationController pushViewController:self.playerVC animated:YES];
}
@end
3. 嵌入子视图(非全屏)
AVPlayerViewController 也可以作为子控制器嵌入,实现小窗口播放:
// 添加为子控制器
[self addChildViewController:self.playerVC];
self.playerVC.view.frame = CGRectMake(0, 100, self.view.bounds.size.width, 200);
[self.view addSubview:self.playerVC.view];
[self.playerVC didMoveToParentViewController:self];
4. AVPlayerViewController 优势
| 特性 | 说明 |
|---|---|
| 自带播放控制 UI | 进度条、播放/暂停、快进快退、全屏、AirPlay、画中画 |
| 支持 modal 和 push | 可全屏也可嵌入导航栈 |
| 支持画中画(PiP) | iPad 上可悬浮小窗口播放 |
| 系统级优化 | 硬件解码、后台播放、锁屏控制 |
| 维护成本低 | 不需要自己写控制 UI |
四、MediaPlayer 框架(已废弃)
1. 简介
MediaPlayer 框架提供了 MPMoviePlayerController 和 MPMoviePlayerViewController 两个类,用于早期 iOS 的视频播放。
重要:这两个类在 iOS 9 被苹果废弃(deprecated),现代开发应使用 AVPlayerViewController(AVKit 框架)替代。以下内容仅作为历史知识和老项目维护参考。
2. MPMoviePlayerController
MPMoviePlayerController 继承自 NSObject,内部有一个 view 用于展示视频,可以添加到任意控制器的 view 上,支持非全屏播放:
#import <MediaPlayer/MediaPlayer.h>
@interface ViewController ()
@property (nonatomic, strong) MPMoviePlayerController *moviePlayer;
@end
@implementation ViewController
- (MPMoviePlayerController *)moviePlayer {
if (!_moviePlayer) {
NSURL *url = [NSURL URLWithString:@"https://example.com/video.mp4"];
_moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:url];
_moviePlayer.view.frame = CGRectMake(0, 100, self.view.bounds.size.width, 200);
// 横竖屏自动调整
_moviePlayer.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:_moviePlayer.view];
}
return _moviePlayer;
}
- (void)play {
[self.moviePlayer play];
}
// 进入全屏
- (void)fullscreen {
[self.moviePlayer setFullscreen:YES animated:YES];
}
@end
3. MPMoviePlayerViewController
MPMoviePlayerViewController 是一个视图控制器,只能全屏播放,通过 modal 方式弹出:
#import <MediaPlayer/MediaPlayer.h>
@interface ViewController ()
@property (nonatomic, strong) MPMoviePlayerViewController *playerVC;
@end
@implementation ViewController
- (MPMoviePlayerViewController *)playerVC {
if (!_playerVC) {
NSURL *url = [NSURL URLWithString:@"https://example.com/video.mp4"];
_playerVC = [[MPMoviePlayerViewController alloc] initWithContentURL:url];
}
return _playerVC;
}
// 只能 modal 全屏播放
- (void)play {
[self presentMoviePlayerViewControllerAnimated:self.playerVC];
}
@end
presentMoviePlayerViewControllerAnimated:是 UIViewController 的 MediaPlayer 分类方法,用于弹出 MPMoviePlayerViewController。
4. 常用监听通知
MPMoviePlayerController 的播放状态通过通知中心监听:
| 通知名 | 触发时机 |
|---|---|
MPMoviePlayerPlaybackStateDidChangeNotification |
播放状态变化(播放/暂停/中断) |
MPMoviePlayerPlaybackDidFinishNotification |
播放完成 |
MPMoviePlayerDidExitFullscreenNotification |
退出全屏 |
MPMoviePlayerThumbnailImageRequestDidFinishNotification |
截屏完成 |
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playbackStateChanged:)
name:MPMoviePlayerPlaybackStateDidChangeNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playbackFinished:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:nil];
// 截屏
[self.moviePlayer requestThumbnailImagesAtTimes:@[@(1.0), @(5.0)] timeOption:MPMovieTimeOptionExact];
5. 从 MediaPlayer 迁移到 AVKit
| MediaPlayer(已废弃) | AVKit(推荐) |
|---|---|
| MPMoviePlayerController | AVPlayer + AVPlayerLayer(自定义 UI)或 AVPlayerViewController |
| MPMoviePlayerViewController | AVPlayerViewController |
play / pause / stop |
[player play] / [player pause](无 stop,用 pause + seekToTime:kCMTimeZero) |
fullscreen 属性 |
AVPlayerViewController 自动支持全屏按钮 |
| 通知监听状态 | KVO 监听 status/timeControlStatus + AVPlayerItemDidPlayToEndTimeNotification |
五、视频格式与第三方解码
1. iOS 原生支持的格式
| 类别 | 支持的格式 |
|---|---|
| 视频编码 | H.264(AVC)、HEVC(H.265,iOS 11+) |
| 音频编码 | AAC、MP3、Apple Lossless、Linear PCM |
| 容器格式 | MP4、M4V、MOV(QuickTime) |
关于 AVI 和 MKV:iOS 原生不支持 AVI 和 MKV 容器格式(虽然 MPMoviePlayerController 可能部分兼容,但不可靠)。播放这些格式需要第三方解码库。
2. 第三方解码框架
| 框架 | 说明 |
|---|---|
| VLCKit | VLC 官方 iOS 库,基于 FFmpeg,支持几乎所有视频格式 |
| FFmpeg | 最流行的开源音视频解码库,功能强大但集成复杂 |
| IJKPlayer | 哔哩哔哩开源的基于 FFmpeg 的播放器,国内广泛使用 |
| KSYMediaPlayer | 金山云开源播放器,基于 IJKPlayer 优化 |
VLC for iOS 地址:https://github.com/videolan/vlc
六、Swift 版本对照
远程音频播放(Swift)
import AVFoundation
let url = URL(string: "https://example.com/audio.m4a")!
let item = AVPlayerItem(url: url)
let player = AVPlayer(playerItem: item)
player.play()
// 进度监听
player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 1, preferredTimescale: 1),
queue: .main) { time in
let current = CMTimeGetSeconds(time)
print("进度: \(current)")
}
// 播放完成
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime,
object: item, queue: .main) { _ in
print("播放完成")
}
视频播放 AVPlayerLayer(Swift)
import AVFoundation
let url = URL(string: "https://example.com/video.mp4")!
let player = AVPlayer(url: url)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.videoGravity = .resizeAspectFill
view.layer.addSublayer(playerLayer)
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
playerLayer.frame = CGRect(x: 0, y: 100, width: view.bounds.width, height: view.bounds.width * 9 / 16)
}
player.play()
AVPlayerViewController(Swift)
import AVKit
import AVFoundation
let url = URL(string: "https://example.com/video.mp4")!
let player = AVPlayer(url: url)
let playerVC = AVPlayerViewController()
playerVC.player = player
playerVC.showsPlaybackControls = true
present(playerVC, animated: true) {
player.play()
}
七、总结
- 远程音频播放:AVAudioPlayer 只支持本地文件,远程流媒体用 AVPlayer;AVPlayer 支持本地和远程 URL,既播音频也播视频;用 AVPlayerItem 包装 URL 便于状态监听和切换曲目;replaceCurrentItemWithPlayerItem 更换曲目。
- AVPlayer 完整配置:① KVO 监听 status(AVPlayerStatusReadyToPlay/Failed)、timeControlStatus(Playing/Paused/Waiting,iOS 10+)、loadedTimeRanges(缓冲进度);② addPeriodicTimeObserver 监听播放进度;③ AVPlayerItemDidPlayToEndTimeNotification 播放完成;④ seekToTime 跳转;⑤ AVAudioSession Playback + Info.plist UIBackgroundModes=audio 支持后台播放;⑥ ATS 限制(HTTP 需例外,推荐 HTTPS)。
- 视频播放 AVPlayerLayer:AVPlayer + AVPlayerLayer 渲染视频画面;frame 应在 viewDidLayoutSubviews 设置(非 viewDidLoad);videoGravity 三种模式:ResizeAspect(默认,留黑边)、ResizeAspectFill(填充裁剪)、Resize(拉伸变形)。
- AVPlayerViewController(现代推荐):iOS 8+,AVKit 框架,自带完整播放控制 UI(进度条、播放/暂停、全屏、AirPlay、画中画),支持 modal 和 push,支持画中画(PiP),替代已废弃的 MPMoviePlayerViewController。
- MediaPlayer 框架(已废弃):MPMoviePlayerController(非全屏,view 添加到父视图)和 MPMoviePlayerViewController(只能全屏 modal),iOS 9 废弃,推荐迁移到 AVKit;播放状态通过通知中心监听(MPMoviePlayerPlaybackStateDidChangeNotification 等);requestThumbnailImagesAtTimes 截屏。
- 视频格式:iOS 原生支持 H.264/HEVC(H.265) 视频编码、AAC/MP3 音频编码、MP4/M4V/MOV 容器;不原生支持 AVI/MKV,需第三方解码(VLCKit/FFmpeg/IJKPlayer)。
- 迁移指南:MPMoviePlayerController → AVPlayer+AVPlayerLayer 或 AVPlayerViewController;MPMoviePlayerViewController → AVPlayerViewController;通知监听 → KVO + AVPlayerItemDidPlayToEndTimeNotification。

浙公网安备 33010602011771号