ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

OC/Swift 知识 AVPlayer音乐播放器的使用(源码)

OC/Swift 知识 AVPlayer音乐播放器的使用(源码) 一直觉得自己写的不是技术而是情怀一个个的教程是自己这一路走来的痕迹。靠专业技能的成功是最具可复制性的希望我的这条路能让你们少走弯路希望我能帮你们抹去知识的蒙尘希望我能帮你们理清知识的脉络希望未来技术之巅上有你们也有我。我把msu8开发好的Swift项目放在这里,后期写一个m3u8播放器文章在移动过去:Swift-(iOS 15 16)m3u8视频播放器VideoPlay实例代码官网文档链接文章目录相关文章音频寻找视频寻找重点1.基本概念1.1.AVPlayer1.2.AVPlayerItem2.基本使用2.1加载本地mp4视频2.2加载网络视频2.3加载本地mp32.4加载网络mp3音乐2.5监听播放结束2.6设置播放完成只有重复播放2.7监听缓存状态等2.8监听是否播放完2.9监听播放进度3.需求解决3.1 获取视频的第一帧返回一张图片3.1.1 本地视频3.1.2 网络视频Bug1.视频列表在飞行模式进来视频详情视频有声音没有画面2.NML(音乐App启动瞬间停止播放,汽水音乐的App)?(重点)拖拽滚动条切换进度,滚动条回弹闪烁的问题.需求1.如何设置支持后台播放OC 设置方法Swift 设置方法2.app启动的时候站停了网易云正在播放的歌曲问题.3.其他App抢占音乐时,如何暂停本App的音乐播放?4.来电抢占音乐如何停止播放,挂电话后自动开启接着播放?相关文章Swift 技术 音频,音乐(AVAudioSession设置,音乐中断)Swift 技术 监听电话中断,音乐(用于恢复播放音乐)(源码)Swift 第三方 播放器AliyunPlayer(阿里云播放器)(源码)Swift 需求 音乐播放暂停淡出淡放(声音逐渐消失)(视频)(源码)OC 技术 DOUAudioStreamer音乐播放器的使用(源码)Swift 基础 AVPlayer音乐播放器的使用(源码)Swift 技术 视频播放器滚动条(源码)音频寻找http://music.163.com/song/media/outer/url?id468176711.mp3开发的时候如何从网上拿到mp3,mp4的链接.咪咕音乐然后点进去,选择点击一首音乐,右键点击网站(检查),看下面图片操作就能拿到播放链接.https://freetyst.nf.migu.cn/public/product9th/product41/2020/09/1514/2020%E5%B9%B409%E6%9C%8804%E6%97%A511%E7%82%B909%E5%88%86%E7%B4%A7%E6%80%A5%E5%86%85%E5%AE%B9%E5%87%86%E5%85%A5%E6%AD%A3%E4%B8%9C25%E9%A6%96258276/%E6%AD%8C%E6%9B%B2%E4%B8%8B%E8%BD%BD/MP3_40_16_Stero/6005662RPV2142203.mp3视频寻找https://vd3.bdstatic.com/mda-kksevzw0s5ap0x6k/hd/cae_h264_nowatermark/1606445103/mda-kksevzw0s5ap0x6k.mp4重点现在很多播放器都会显示一个播放器进度条加载的显示过程,但是iOS的控件里面没有这种控件,系统就提供了UISlider这个控件.所以我自己封装了一个可以看到播放器的封装.并写了一遍文件记录整个封装的过程和解说Swift 技术 视频播放器滚动条(源码)自定义视频播放滑进度条-示例代码1.基本概念1.1.AVPlayerAVPlayer是一个用来播放的对象,支持播放本地,分步下载,或者通过HLS协议得到的流媒体.它是一个不可见的组件,如果播放mp3mp4类的音频文件,可以.但是如果要想播放视频文件,我们就需要了解另外一个类AVPlayerLayerAVPlayerLayer它是对于CALayer类的扩展,通过框架在屏幕上显示内容,作为视频的渲染面,然后在用户面前进行展示.在创建AVPlayerLayer时,同时需要一个指向 AVPlayer的指针,把两者联系在一起.1.2.AVPlayerItem说白了,AVPlayerItem是一个载体,承载AVAsset,然后通过AVPlayer进行播放.我们要想对一个资源进行播放,那么就要通过AVPlayerItem和AVPlayerItemTrack来构建对应的动态内容2.基本使用2.1加载本地mp4视频注意视频一加载就会播放Swiftlet filePath Bundle.main.path(forResource: SuchAs, ofType: mp4) let videoURL URL(fileURLWithPath: filePath!) playerItem AVPlayerItem(url: videoURL) player AVPlayer(playerItem: playerItem) player.rate 1.0//播放速度 播放前设置 //创建显示视频的图层 let playerLayer AVPlayerLayer.init(player: player) playerLayer.videoGravity .resizeAspect playerLayer.frame CGRect(x: 0, y: 0, width: 150, height: 300) playView.layer.addSublayer(playerLayer) player.pause() //停止播放OC#import ViewController.h #import AVFoundation/AVFoundation.h interface ViewController () property (nonatomic, strong) UIView *playView; end implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.playView [UIView new]; [self.view addSubview:self.playView]; self.playView.frame CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height); [self playerLoaderVideo]; } -(void)playerLoaderVideo { NSString *filePath [[NSBundle mainBundle] pathForResource:0 ofType:mov]; NSURL *videoURL [NSURL fileURLWithPath:filePath]; AVPlayerItem *playerItem [AVPlayerItem playerItemWithURL:videoURL]; AVPlayer *player [AVPlayer playerWithPlayerItem:playerItem]; player.rate 1.0; // 播放速度 播放前设置 // 创建显示视频的图层 AVPlayerLayer *playerLayer [AVPlayerLayer playerLayerWithPlayer:player]; playerLayer.videoGravity AVLayerVideoGravityResizeAspect; playerLayer.frame CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height); [self.playView.layer addSublayer:playerLayer]; //[player pause]; // 停止播放 } end2.2加载网络视频let videoURL URL(string: http://n1cdn.miaopai.com/stream/1UKfVpOmazRYEb4fVejwhgpX~3uIxmHBV~8VCQ___0_1506471211.mp4?ssige9a0601e2c3261e5c6b6c91a1111ced3time_stamp1652542020536) playerItem AVPlayerItem(url: videoURL!) player AVPlayer(playerItem: playerItem) player.rate 1.0//播放速度 播放前设置 //创建显示视频的图层 let playerLayer AVPlayerLayer.init(player: player) playerLayer.videoGravity .resizeAspect playerLayer.frame CGRect(x: 0, y: 0, width: 150, height: 300) playView.layer.addSublayer(playerLayer) player.pause()2.3加载本地mp3let filePath Bundle.main.path(forResource: Faded, ofType: mp3) let videoURL URL(fileURLWithPath: filePath!) let playerItem AVPlayerItem(url: videoURL) player AVPlayer(playerItem: playerItem) player.rate 1.0//播放速度 播放前设置2.4加载网络mp3音乐let videoURL URL(string: self.model.PronounceUrl) let playerItem AVPlayerItem(url: videoURL!) player AVPlayer(playerItem: playerItem) player.rate 1.0//播放速度 播放前设置 player.play()2.5监听播放结束NotificationCenter.default.addObserver(self, selector: #selector(playToEndTime), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)//监听播放结束 objc func playToEndTime(){ print(播放完成) }deinit { //监听播放结束 NotificationCenter.default.removeObserver(self) }2.6设置播放完成只有重复播放objc func playToEndTime(){ player?.seek(to: CMTime(value: 0, timescale: 1)) player?.play() }2.7监听缓存状态等//观察属性 self.playerItem.addObserver(self, forKeyPath: status, options: .new, context: nil) //缓存区间可用来获取缓存了多少 self.playerItem.addObserver(self, forKeyPath: loadedTimeRanges, options: .new, context: nil) //缓存不够了 自动暂停播放 self.playerItem.addObserver(self, forKeyPath: playbackBufferEmpty, options: .new, context: nil) //缓存好了 手动播放 self.playerItem.addObserver(self, forKeyPath: playbackLikelyToKeepUp, options: .new, context: nil)//KVO观察 override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if keyPath status { switch self.playerItem.status{ case .readyToPlay: //player.play() print(play) case .failed: print(failed) case.unknown: print(unkonwn) default: break } }else if keyPath loadedTimeRanges{ let loadTimeArray self.playerItem.loadedTimeRanges //获取最新缓存的区间 let newTimeRange : CMTimeRange loadTimeArray.first as! CMTimeRange let startSeconds CMTimeGetSeconds(newTimeRange.start); let durationSeconds CMTimeGetSeconds(newTimeRange.duration); let totalBuffer startSeconds durationSeconds;//缓冲总长度 print(当前缓冲时间%f,totalBuffer) }else if keyPath playbackBufferEmpty{ print(正在缓存视频请稍等) } else if keyPath playbackLikelyToKeepUp{ print(缓存好了继续播放) //self.player.play() } }2.8监听是否播放完//用于实时监听滑块的进度 self.player.addPeriodicTimeObserver(forInterval: CMTimeMake(value: 1, timescale: 1), queue: DispatchQueue.main) { [weak self](time) in //当前正在播放的时间 let loadTime CMTimeGetSeconds(time) //视频总时间 let totalTime CMTimeGetSeconds((self?.player.currentItem?.duration)!) //滑块进度 self?.slider.value Float(loadTime/totalTime) self?.loadTimeLabel.text self?.changeTimeFormat(timeInterval: loadTime) self?.totalTimeLabel.text self?.changeTimeFormat(timeInterval: CMTimeGetSeconds((self?.player.currentItem?.duration)!)) }//转时间格式 func changeTimeFormat(timeInterval:TimeInterval) - String{ return String(format: %02d:%02d:%02d,(Int(timeInterval) % 3600) / 60, Int(timeInterval) / 3600,Int(timeInterval) % 60) }2.9监听播放进度#pragmamark-设置 播放了的时间 滚动条的播放进度 播放结束时间-(void)getProgress{//用于在指定的时间间隔内定期触发回调来更新播放进度或执行其他操作。/** * param Interval : 指定回调触发的时间间隔(最少要1秒) * param queue : 指定回调的执行队列一般使用主队列DispatchQueue.main * param block : 回调闭包接收一个CMTime参数表示当前播放的时间 * * return nil * * */[[AVPlayerManager new].player addPeriodicTimeObserverForInterval:CMTimeMake(1.0,1.0)queue:dispatch_get_main_queue()usingBlock:^(CMTime time){//获取当前的播放了的时间NSString*startTime[selfformatTimeWithTimeInterVal:CMTimeGetSeconds(time)];//歌曲的总时间NSString*endTime[selfformatTimeWithTimeInterVal:CMTimeGetSeconds([AVPlayerManager new].playerItem.duration)];//获取当前的播放了的时间floatpercentCMTimeGetSeconds(time)/CMTimeGetSeconds([AVPlayerManager new].playerItem.duration);floatvaluepercent*100;self.progressBlock(startTime,endTime,value);}];}3.需求解决3.1 获取视频的第一帧返回一张图片3.1.1 本地视频import UIKit import AVFoundation class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor .white let thumb UIImageView.init(frame: CGRect.init(x: 50, y: 200, width: 300, height: 200)) self.view.addSubview(thumb) let filePath Bundle.main.path(forResource: SuchAs, ofType: mp4) let videoURL URL(fileURLWithPath: filePath!) let asset AVURLAsset.init(url: videoURL, options: nil) let gen AVAssetImageGenerator.init(asset: asset) gen.appliesPreferredTrackTransform true let time CMTimeMakeWithSeconds(0.0, preferredTimescale: 1) var actualTime : CMTime CMTimeMakeWithSeconds(0, preferredTimescale: 0) do { let image try gen.copyCGImage(at: time, actualTime: actualTime) thumb.image UIImage.init(cgImage: image) } catch { print(错误) } } }3.1.2 网络视频import UIKit import AVFoundation class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor .white let thumb UIImageView.init(frame: CGRect.init(x: 50, y: 200, width: 300, height: 200)) self.view.addSubview(thumb) let asset AVURLAsset.init(url: URL.init(string: http://gslb.miaopai.com/stream/1UKfVpOmazRYEb4fVejwhgpX~3uIxmHBV~8VCQ__.mp4)!, options: nil) let gen AVAssetImageGenerator.init(asset: asset) gen.appliesPreferredTrackTransform true let time CMTimeMakeWithSeconds(0.0, preferredTimescale: 1) var actualTime : CMTime CMTimeMakeWithSeconds(0, preferredTimescale: 0) do { let image try gen.copyCGImage(at: time, actualTime: actualTime) thumb.image UIImage.init(cgImage: image) } catch { print(错误) } } }Bug1.视频列表在飞行模式进来视频详情视频有声音没有画面一般视频有声音没有画面多数都是AVPlayerLayer有关系的。那么这次的原因是因为使用系统监听的网络封装从飞行模式转换到wifi模式之后当时在异步线程刷新UI导致的。修改办法2.NML(音乐App启动瞬间停止播放,汽水音乐的App)?1.如何做到启动顺便停止汽水音乐的App的音乐播放?2.如果我想在点击播放按键的时候才停止播放,音乐的App的音乐播放.如何修改?造成该原因是因为下面的代码造成的.一旦启动就会停止,汽水音乐正在播放的音乐.因为App启动的时候,category默认的设置是:AVAudioSessionCategorySoloAmbient只要把上面的代码不要写在Appdelegate里面启动,就不会停止汽水音乐播放的音乐.那么上面的第二个问题,如果我想在点击播放按键的时候才停止播放,音乐的App的音乐播放.如何修改?把这句代码写在点击播放的哪里,也就是播放才设置.(重点)拖拽滚动条切换进度,滚动条回弹闪烁的问题.该bug应该写在视频播放器里面,目前先总结到这里滑动闪烁的问题修好-滑动没有问题造成这个问题的原因是:当修改进度的时候(player.seek(to: seekTime)),并不是马上就能获取当前的进度就是修改好的进度,监听获取当前进度(player.addPeriodicTimeObserver(forInterval:),在没有修改进度成功之间返回的进度还是没有拖拽的进度.所以就会出现拖拽进度后,出现闪烁的问题(闪烁回去没有修改的进度点哪里). 修改方法1.新建一个属性,当拖拽之后,(player.addPeriodicTimeObserver(forInterval:)监听进度方法禁止更新.2.当拖拽进度之后,等到进度修改成功了,更新当前进度条,之后才去允许,(player.addPeriodicTimeObserver(forInterval:)监听进度方法更新.需求1.如何设置支持后台播放步骤1.info.plist直接设置keyUIBackgroundModes/key array stringaudio/string /array或者也可以向下面的设置也是同样的效果我发现这样设置之后好像已经支持后台播放,不过网上很多还需要下面的设置.OC 设置方法写在appDelegateAVAudioSession *session [AVAudioSession sharedInstance]; [session setActive:YES error:nil]; [session setCategory:AVAudioSessionCategoryPlayback error:nil];不过要是想 后台播放下一曲还需要再添加一行代码[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];Swift 设置方法写在appDelegate//设置音乐的播放模式 private func setAudioSupport(){ let session AVAudioSession.sharedInstance() do { try session.setCategory(AVAudioSession.Category.playback) try session.setActive(true) } catch { print(error) } UIApplication.shared.beginReceivingRemoteControlEvents() }2.app启动的时候站停了网易云正在播放的歌曲问题.2023.5.3日修改一个非常头疼的问题,当时在2022.11月份的时候修改NML,JAZZ,WORLD,的时候也修改过的同样出现这个问题,后来隔了很久,NSWL也发现这个问题,测试了很久才会想起来,之前是修过的,但是也花了很长时间才测试出来.主要原因有两个;原因一:启动的时候重新设置播放规则,看下面代码.设置这个的目的是为了能够支持后台播放,但是我发现,注释了一样能支持后台播放,我还以SDK本身启动的时候有设置,这个我就不管它了原因二:app启动把音乐加载到iPhone的播放器里面,占用的网易云在iPhone播放器里面的音乐,因为不管app有多少个,硬件的播放器就只有一个,如果想播放音乐,就需要把软件的音乐放到硬件的播放器里面对音乐进行播放.NML,JAZZ,WORLD,NSWL 这些app有一个本身有一个需求,如果app整个播放的情况下,杀死了app,重新启动的时候需要把迷你播放器或者播放器详情重新弹出来,然后点击播放按键就开始播放音乐.问题就在于,app启动的时候把保存在本地里面的歌曲拿出来,然后谈起播放器详情的信息进行显示,然后把播放管理器的歌曲放到硬件的播放器里面,网易云正在播放的音乐就停止了.,原因就在前面红色的字体里.拿到本地的歌曲赋值UI同时加载音乐.修改它有两个办法:1.app启动的时候,歌曲赋值UI就是赋值UI,歌曲放到硬件播放器,这样把这个两个步骤单独分开.2.app启动的时候,点击播放,切换歌曲的时候,才把本地的歌曲赋值到播放管理器的模型里面目前修改为下面的代码,思想主要是上面核心的问题,下面自己用于回想当时的情景,最好的修改方法应该是:app启动的时候,歌曲赋值UI就是赋值UI,歌曲放到硬件播放器,这样把这个两个步骤单独分开.3.其他App抢占音乐时,如何暂停本App的音乐播放?2023.03.23 NSWL遇到一个问题,当NSWL正在播放音乐的时候,然后打开抖音播放视频,或者打开汽水音乐app播放音乐,这个时候NSWL的音乐被中断了,我们应该App收到通知去暂停播放音乐.但是NSWL音乐是中断了,但是AliyunPlayer的SDK并没有提供(抢占音乐)代理,所以无法进行设置暂停播放的操作.但是NML使用 DOUAudioStreamer SDK播放音乐当抢占音乐的时候代理是有回调的,所以设置很方便那么AliyunPlayer既然没有这个功能,我们应该如何处理这个bug,看下面的逻辑图就明白了下面的主要的代码// 检测中断音乐通知 NotificationCenter.default.addObserver(self, selector: #selector(interruptionNotification(notifi:)), name: AVAudioSession.interruptionNotification, object: nil)// 检测中断音乐通知 objc func interruptionNotification(notifi:Notification) { //false 中断结束 true 中断开始 print(Key: \(notifi.userInfo?[AVAudioSessionInterruptionTypeKey] as? Bool)) let suspend notifi.userInfo?[AVAudioSessionInterruptionTypeKey] as? Bool ?? false if suspend { NotificationCenter.default.post(name: NSNotification.Name.suspendNotification, object: nil) } }上面的解决方法,我写在播放管理起里面的,有时候我怕出发不了,毕竟那时候倍抢占了音乐,自己的app肯定在后台,所以我在Appdelegate里面也写多了一个代码,用于出发暂停按键的设置,看自己的需求,有空的话可以写一下.// 检测中断音乐通知NotificationCenter.default.addObserver(self,selector:#selector(interruptionNotification(notifi:)),name:AVAudioSession.interruptionNotification,object:nil)// 检测中断音乐通知objc funcinterruptionNotification(notifi:Notification){//false 中断结束 true 中断开始//print(Key: \(notifi.userInfo?[AVAudioSessionInterruptionTypeKey] as? Bool))let suspendnotifi.userInfo?[AVAudioSessionInterruptionTypeKey]as?Bool??falseifsuspend{NotificationCenter.default.post(name:NSNotification.Name.suspendNotification,object:nil)}}4.来电抢占音乐如何停止播放,挂电话后自动开启接着播放?写好之后NSWL没有上架过不知道能够上架,听说CallKit在中国禁用了.下面的方法就是用CallKit解决的不行的话就使用CoreTelephony下面的代码写在AppDelegate里面的.UIApplicationDelegate///属性记录电话 打来中断private let callObserverCXCallObserver()callObserver.setDelegate(self,queue:nil)extension AppDelegate:CXCallObserverDelegate{funccallObserver(_ callObserver:CXCallObserver,callChanged call:CXCall){if!call.isOutgoing!call.isOnHold!call.hasConnected!call.hasEnded{// 暂停播放ifAliPlayerManger.default.playerState1{AliPlayerManger.default.playerState0callPhoneOutGoingtrue AliPlayerManger.default.pause()}}elseif!call.isOutgoing!call.isOnHold!call.hasConnectedcall.hasEnded{// 开始播放//print(来电-挂掉(未接通))ifAliPlayerManger.default.playerState0callPhoneOutGoingtrue{AliPlayerManger.default.playerState1callPhoneOutGoingfalse AliPlayerManger.default.play()}}elseif!call.isOutgoing!call.isOnHoldcall.hasConnected!call.hasEnded{// 暂停播放//print(来电-接通)ifAliPlayerManger.default.playerState1{AliPlayerManger.default.playerState0callPhoneOutGoingtrue AliPlayerManger.default.pause()}}elseif!call.isOutgoing!call.isOnHoldcall.hasConnectedcall.hasEnded{// 开始播放//print(来电-接通-挂掉)ifAliPlayerManger.default.playerState0callPhoneOutGoingtrue{AliPlayerManger.default.playerState1callPhoneOutGoingfalse AliPlayerManger.default.play()}}elseifcall.isOutgoing!call.isOnHold!call.hasConnected!call.hasEnded{// 暂停播放//print(拨打)ifAliPlayerManger.default.playerState1{AliPlayerManger.default.playerState0callPhoneOutGoingtrue AliPlayerManger.default.pause()}}elseifcall.isOutgoing!call.isOnHold!call.hasConnectedcall.hasEnded{// 开始播放//print(拨打-挂掉(未接通))ifAliPlayerManger.default.playerState0callPhoneOutGoingtrue{AliPlayerManger.default.playerState1callPhoneOutGoingfalse AliPlayerManger.default.play()}}elseifcall.isOutgoing!call.isOnHoldcall.hasConnected!call.hasEnded{// 暂停播放//print(拨打-接通)ifAliPlayerManger.default.playerState1{AliPlayerManger.default.playerState0callPhoneOutGoingtrue AliPlayerManger.default.pause()}}elseifcall.isOutgoing!call.isOnHoldcall.hasConnectedcall.hasEnded{// 开始播放//print(拨打-接通-挂掉)ifAliPlayerManger.default.playerState0callPhoneOutGoingtrue{AliPlayerManger.default.playerState1callPhoneOutGoingfalse AliPlayerManger.default.play()}}}}
返回列表