当前位置: 首页 > news >正文

【iOS】音频与视频播放

【iOS】音频与视频播放

  • 前言
  • 视频播放
    • AVPlayer、AVPlayerLayer、AVPlayerItem
    • 具体实现
      • AVPlayer+AVPlayerLayer手动自定义播放器界面
      • 系统自带完整播放器界面
    • 更多功能
  • 音频播放
    • 短音频播放
    • 长音效播放
  • 总结

前言

在iOS应用中集成音频和视频播放功能是常见的需求。iOS提供了强大的框架来支持多媒体内容的播放,主要是AVFoundation框架。

视频播放

iOS中想播放视频,那么就要用到AVFoundation库,在AVFoundation框架中,视频播放主要由数据层AVAsset、播放单元层AVPlayerItem和播放器层AVPlayer构成。

AVPlayer、AVPlayerLayer、AVPlayerItem

  • AVPlayer:是播放媒体内容的核心对象,负责播放控制逻辑(播放、暂停、跳转、速率等)。
  • AVPlayerLayer:是CALayer的子类,显示视频画面的图层,是AVPlayer的可视化层。
  • AVPlayerItem:是连接媒体文件和播放器的中间层,起承上启下作用。

在这里插入图片描述

具体实现

具体实现有两种方式:

  • 直接使用AVPlayer+AVPlayerLayer手动构建播放器UI。
  • 使用系统自带完整播放器界面。

AVPlayer+AVPlayerLayer手动自定义播放器界面

  1. 初始化一个播放单元

这里我们的URL要明确视频的路径是本地还是网络。

  • 网络URL:
    NSURL *url = [NSURL URLWithString:@"/Users/mac/Desktop/技能五子棋.mp4"];
    
  • 本地文件:
    • 如果视频在本地某个位置:
    NSString *path = @"/Users/mac/Desktop/技能五子棋.mp4";
    NSURL *url = [NSURL fileURLWithPath:path];
    
    • 如果视频在项目Bundle里:
    NSString *path = [[NSBundle mainBundle] pathForResource:@"技能五子棋" ofType:@"mp4"];NSURL *url = [NSURL fileURLWithPath:path];
    
self.item = [AVPlayerItem playerItemWithURL:url];
  1. 初始化一个对象播放器
self.player = [AVPlayer playerWithPlayerItem:self.item];
  1. 初始化一个播放界面
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
self.playerLayer.frame = CGRectMake(100, 200, screenWidth - 200, 500);

我们会发现无论如何修改self.playerLayer.frame,视频宽高比例永远不会变化,这是因为self.playerLayer.frame控制的是显示在屏幕上的矩形区域大小。

而self.playerLayer.videoGravity控制视频内容如何在外框中铺放。它有以下三种常用值:

  • self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;:按比例缩放,不裁剪、不拉伸,默认

在这里插入图片描述

  • self.playerLayer.videoGravity = AVLayerVideoGravityResize;:不保持比例直接拉伸

在这里插入图片描述

  • self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;:按比例缩放,充满视图,可能裁剪部分画面
    在这里插入图片描述
  1. 开始播放视频
[self.view.layer addSublayer:self.playerLayer];
[self.player play];

这里有一个问题,为什么不是直接把playerLayer作为一个控件直接加到self.view上?

我们会发现系统给出警告:类型不兼容,不能把CALayer当作UIView添加到视图层级中。

在这里插入图片描述

这是因为AVPlayerLayer是CALayer的子类,它本身不是UIView,就不能直接加到视图层级上。

在iOS绘图系统中,每一个UIView背后都有一个CALyer,称作宿主图层,所有视图的绘制其实都是它的layer在负责显示。UIView负责响应点击、滑动等事件和布局,CALayer负责显示颜色、图片、视频、动画等内容。形象地说,UIView是一块透明玻璃,view.layer是玻璃上能显示内容的那层薄膜,AVPlayerLayer是贴上去播放视频的胶片。

这样,就实现了视频的播放。

在这里插入图片描述

系统自带完整播放器界面

我们先了解一下AVPlayerViewController。

AVPlayerViewController属于AVKit,是苹果官方提供的系统控件。其作用是帮我们快速创建带系统自带UI的视频播放器,包括播放/暂停按钮、时间进度条、音量控制、全屏切换、AirPlay支持,我们自己是不用写这些控件的。

AVPlayerViewController是UIViewController的子类,这意味着它与普通ViewController一样,能被presentViewController弹出,它拥有UIViewController的所有生命周期。它可以自动处理旋转、全屏、播放结束通知。

值得注意的:AVPlayerViewController本身不负责解码视频,它只是显示和控制。解码和播放由AVPlayer完成。

-(void)playVideoWithViewController {NSLog(@"开始");NSString *path = [[NSBundle mainBundle] pathForResource:@"技能五子棋" ofType:@"mp4"];NSLog(@"%@", path);NSURL *url = [NSURL fileURLWithPath:path];//创建一个系统自带的视频播放控制器//将AVPlayer交给它负责AVPlayer *player = [AVPlayer playerWithURL:url];AVPlayerViewController *playerVC = [[AVPlayerViewController alloc] init];playerVC.player = player;[self presentViewController:playerVC animated:YES completion:^{[player play];}];
}

然而视频却没有显示在屏幕上,这是什么原因?
这是因为根据先前了解的UIViewController生命周期:loadView → viewDidLoad → viewWillAppear → viewDidAppear,在viewDidload中调用playVideoWithViewController时,当前视图控制器还未执行到viewDidAppear,即当前视图控制器ViewController没有真正显示在屏幕上,这时候present的视图控制器也不会显示,因此我们将调用playVideoWithViewController写在viewDidAppear:中。

-(void)viewDidAppear:(BOOL)animated {[super viewDidAppear:animated];[self playVideoWithViewController];
}

在这里插入图片描述

更多功能

  1. UISlider实时显示和调整进度
//实时刷新slider和当前时间
-(void)updateSlider {CGFloat time = self.item.currentTime.value / self.item.currentTime.timescale;self.slider.value = time;self.leftLb.text = [self formatTime:time];
}
//格式化时间
-(NSString*)formatTime:(NSInteger)time {NSInteger min = time / 60;NSInteger sec = time % 60;return [NSString stringWithFormat:@"%02ld:%02ld", (long)min, (long)sec];
}
//滑动调节播放进度
-(void)silderValueChanged {CGFloat seconds = self.slider.value;self.leftLb.text = [self formatTime:(NSInteger)seconds];CMTime startTime = CMTimeMakeWithSeconds(seconds, self.item.currentTime.timescale);//让播放器跳转到startTime处,completionHandler在跳转完成后回调[self.player seekToTime:startTime completionHandler:^(BOOL finished) {if (self.isPlaying) {[self.player play];}}];
}

CMTime:AVFoundation中表示时间的结构体。

typedef struct
{CMTimeValue	value;		/*!< The value of the CMTime. value/timescale = seconds */CMTimeScale	timescale;	/*!< The timescale of the CMTime. value/timescale = seconds. */CMTimeFlags	flags;		/*!< The flags, eg. kCMTimeFlags_Valid, kCMTimeFlags_PositiveInfinity, etc. */CMTimeEpoch	epoch;		/*!< Differentiates between equal timestamps that are actually different becauseof looping, multi-item sequencing, etc.Will be used during comparison: greater epochs happen after lesser ones.Additions/subtraction is only possible within a single epoch,however, since epoch length may be unknown/variable */
}

简单来说,CMTime = value(时间的数值)/timescale(时间刻度)秒

  1. 实时显示播放时间
-(void)pressBtn {if (self.isPlaying) {[self.player pause];[self.btn setTitle:@"播放" forState:UIControlStateNormal];//暂停定时器[self.timer invalidate];self.timer = nil;} else {[self.player play];[self.btn setTitle:@"暂停" forState:UIControlStateNormal];[self startTimer];}self.isPlaying = !self.isPlaying;
}-(void)startTimer {self.timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(updateSlider) userInfo:nil repeats:YES];//将定时器加到NSRunLoopCommonModes模式中,避免用户拖动silder或滚动界面时RunLoop切换模式,定时器暂停,界面进度条卡住//NSDefaultRunLoopMode:普通状态,用户未滚动UI//UITrackingRunLoopMode:用户正在滑动UIScollView或拖动UISlider//NSRunLoopCommonModes:集中常用模式的集合模式[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
  1. 获取视频总时长

这里使用了KVO传值的方式获取视频总时长。(不要忘了销毁监听!)

[self.item addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {if ([keyPath isEqualToString:@"status"]) {AVPlayerStatus status = [change[NSKeyValueChangeNewKey] integerValue];if (status == AVPlayerStatusReadyToPlay) {NSLog(@"视频好了");CGFloat duration = self.item.duration.value / self.item.duration.timescale;self.slider.maximumValue = duration;self.rightLb.text = [self formatTime:duration];} else if (status == AVPlayerStatusFailed) {NSLog(@"视频加载失败");}}
}-(void)dealloc {[self.item removeObserver:self forKeyPath:@"status"];[self.timer invalidate];
}

展示一下实现功能的效果:

在这里插入图片描述

音频播放

iOS中播放音频主要有两种场景:

  • 短音频:音效、提示音
  • 长音频:音乐、博客

短音频播放

适用于播放时长较短(小于30秒) 、文件较小、不需要精确控制播放进度的音频文件。

具体步骤如下:

  1. 导入头文件
#import <AudioToolbox/AudioToolbox.h>
  1. 获取音频文件URL
NSString *path = [[NSBundle mainBundle] pathForResource:@"ding" ofType:@"wav"];
NSURL *soundURL = [NSURL fileURLWithPath:path];
  1. 创建系统声音ID

AudioServicesCreateSystemSoundID:根据URL创建一个系统声音对象,并返回一个可用ID。执行成功后,系统会将音频文件加载成可播放的系统声音。

extern OSStatus 
AudioServicesCreateSystemSoundID(   CFURLRef                    inFileURL,SystemSoundID*              outSystemSoundID)API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

参数1:音频文件的路径(要先转化为CFURLRef)

参数2:一个输出参数的地址,填入创建成功后的SystemSoundID

SystemSoundID:是一个系统定义的整型标识符,用来表示某个音效,系统通过上面返回的ID管理声音的播放和释放等。

//创建SystemSoundID
SystemSoundID soundID;//声明SystemSoundID类型变量
//__bridge CFURLRef:ARC环境下,只进行类型转换,不会改变对象引用计数,使用桥接关键字保证内存安全
AudioServicesCreateSystemSoundID((__bridge CFURLRef)soundURL, &soundID);//创建SystemSoundID
  1. 播放声音
//播放声音
AudioServicesPlaySystemSound(soundID);
//播放完成后释放
//AudioServicesDisposeSystemSoundID(soundID);

通过一个按钮展示一下效果:

在这里插入图片描述

除此之外,Apple为开发者保留了一些可公开调用的系统音效ID,同样可以通过AudioServicesPlaySystemSound播放,常用的如下:

在这里插入图片描述

AudioServicesPlaySystemSound(1007);
AudioServicesPlaySystemSound(1022);

这些ID在系统内部预定义,可直接使用。

在这里插入图片描述

长音效播放

适用于播放时长较长、需要精确控制播放进度、音量、循环、支持后台播放的音频文件。常用于播放背景音乐、录音回放等。

具体步骤如下:

  1. 导入头文件
#import <AVFoundation/AVFoundation.h>
  1. 创建音频URL
NSString *path = [[NSBundle mainBundle] pathForResource:@"很久很久" ofType:@"mp3"];
NSURL *url = [NSURL fileURLWithPath:path];
  1. 初始化AVAudioPlayer,并设置部分属性
NSError *error = nil;
self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
//默认播放一次:self.audioPlayer.numberOfLoops = 0;
//self.audioPlayer.numberOfLoops = 3;播放3次,加上初始1次,共4次
self.audioPlayer.numberOfLoops = -1;//表示无限循环
self.audioPlayer.volume = 0.5;//设置音量(0.0-1.0)

AVAudioPlayer是AVFoundation框架中专门用于播放本地音频文件的类。

而播放网络音频,则应该使用AVPlayer或更高级的AVQueuePlayer、AVPlayerItem。

-(void)playOnlineMusic {NSURL *url = [NSURL URLWithString:@"https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3"];AVPlayerItem *item = [AVPlayerItem playerItemWithURL:url];self.player = [AVPlayer playerWithPlayerItem:item];//创建音频会话AVAudioSession *session = [AVAudioSession sharedInstance];//设置会话类别//AVAudioSessionCategoryAmbient:可播放音频,与系统或其他App的声音混合//AVAudioSessionCategorySoloAmbient:播放音频,但独占输出,打断其他App//AVAudioSessionCategoryPlayback:播放音频,可后台播放,不被静音键影响//AVAudioSessionCategoryRecord:录音专用//AVAudioSessionCategoryPlayAndRecord:同时播放和录音,适用于语音通话聊天等[session setCategory:AVAudioSessionCategoryPlayback error:nil];//激活音频会话,让设置立即生效[session setActive:YES error:nil];[self.player play];
}
  1. 准备并播放
if ([self.audioPlayer prepareToPlay])
{[self.audioPlayer play];
} else {NSLog(@"播放失败");
}
  1. 更多功能
  • 中途暂停、停止播放冲头开始
-(void)pauseSound {if (self.audioPlayer.isPlaying) {[self.audioPlayer pause];NSLog(@"暂停");} else {NSLog(@"当前未在播放状态");}
}-(void)stopSound {if (self.audioPlayer.isPlaying) {[self.audioPlayer stop];self.audioPlayer.currentTime = 0;//停止并回到开头NSLog(@"音频已停止");} else {NSLog(@"当前未在播放状态");}
}
  • 代理协议实现回调
@interface ViewController ()<AVAudioPlayerDelegate>
self.audioPlayer.delegate = self;
//播放完成回调
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {if (flag) {NSLog(@"音频播放完成");} else {NSLog(@"音频播放中断");}
}//解码错误回调
-(void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error {NSLog(@"解码错误:%@", error.localizedDescription);
}

在这里插入图片描述

但是我们发现一个问题,暂停后继续播放时又会重新开始。我们解决这个问题:

if (!self.audioPlayer) {//初始化播放器
}
if (!self.audioPlayer.isPlaying) {[self.audioPlayer play];NSLog(@"继续播放");
} else {NSLog(@"已经在播放中");
}

通过判断播放器是否已经创建,防止反复初始化,并且判断是否是暂停状态,是则继续播放。

AVAudioPlayer内部会自动维护播放进度,调用pause暂停后,再次调用play会从上次暂停出继续播放,只有调用stop并将currentTime设置为0,才会回到开头。

这样,我们就实现了长音频播放、暂停、停止功能:
在这里插入图片描述

总结

对音频和视频播放的学习将会对笔者后续写项目有很大的帮助。除此之外,对后台播放、中断处理等笔者学习后将补充完善该博客。

http://www.dtcms.com/a/585120.html

相关文章:

  • php通过身份证号码计算年龄
  • 基于PHP+Vue+小程序快递比价寄件系统
  • Next.js、NestJS、Nuxt.js 是 **Node.js 生态中针对不同场景的框架**
  • 牛客周赛 Round 114 Java题解
  • PostgreSQL 中数据库、用户、对象关系、表、连接及管理概述
  • 樟树市城乡规划建设局网站爱站攻略
  • Gitblit 迁移指南
  • Git分支管理核心:git fetch与git checkout创建分支完全指南
  • LRU 缓存的设计与实现
  • Linux -- 线程互斥
  • 2.2 Transformer 架构详解:从理论到实践
  • 《Docker+New Relic+Jenkins:开发全链路的工具赋能指南》
  • 2025最新修复的豪门足球风云-修复验证问题-修复注册问题实现地注册-架设教程【豪门足球本地验证】
  • 【Linux笔记】网络部分——数据链路层mac-arp
  • 深圳网站设计公司专业吗外国网站分享代码
  • VB.Net 常用函数
  • 成都哪家做网站wordpress 主题课堂
  • 智慧随访管理系统源码,基于Java+Spring Boot+Vue的随访系统源码,支持二次开发,支持患者信息管理、多类型随访、三级回访机制、问卷模板
  • MQL5 自学路线图:从入门到实战
  • 告别 mysqldump 痛点!用 mydumper 实现 MySQL 高效备份与恢复
  • 【Java 并发编程】线程创建 6 种方式:Thread/Runnable/Callable 核心类全解析
  • Lombok.jar bug
  • 隐藏在字符编码中的陷阱:深入剖析宽字节注入
  • STM32外设学习--TIM定时器--编码器接口(程序)
  • iis 网站关闭陕西省住房和城乡建设厅
  • 【C++】多态与虚函数
  • 洛谷 P9847 [ICPC 2021 Nanjing R] Crystalfly
  • X光机AI系统实现轮胎缺陷识别准确率超97%
  • Depth Anything with Any Prior解读
  • Vue2 学习记录--语法部分