因为项目里有查看监控的需要,是 rtmp格式的 的,看了相关的框架,最后决定用 b 站哒
主要参照的是这个教程iOS中集成ijkplayer视频直播框架 ,虽然写得有点早,但是流程基本是一致的,我只是做一下笔记,以及补充一些自己遇到的问题,希望能帮到大家啦
真的就是,手把手教学哎
零 准备工作
需要 brew,yasm,pkg-config ,用一下命令查询即可
brew -v
git --version
yasm --version
我没有查,因为记得没装yasm ,就直接装了。brew一般都有装的了
brew install yasm
一 下载
- 下载地址 Ijkplayer
二 编译
-
终端进入到解压后的文件夹
执行命令
./init-ios.sh
下载ffmpeg ,等待ing执行命令
cd ios
进入ios 文件夹编译 ffmpeg ,依次执行:
./compile-ffmpeg.sh clean
和./compile-ffmpeg.sh all
也是需要等一下,有点慢
遇到的问题,没问题请跳到下一步
- 执行````./compile-ffmpeg.sh all ```报错
C compiler test failed.
If you think configure made a mistake, make sure you are using the latest
version from Git. If the latest version fails, report the problem to the
ffmpeg-user@ffmpeg.org mailing list or IRC #ffmpeg on irc.freenode.net.
Include the log file "ffbuild/config.log" produced by configure as this will help
solve the problem.
参考 [编写shell脚本编译ffmpeg iOS静态库出错] (https://www.jianshu.com/p/985d4c39666a)
第一步
1)检查你的 git 版本
2)确保你使用的是最新的 git 版本
第二步
1)在日志文件(ffmpeg-armv7/ffbuild/config.log)中使用关键字(xcrun: error)搜索错误详情
2)例如:你会发现 “xcrun: error: SDK "iphoneos” 不能被定位到
第三步
1)在终端输入 sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer/
2)输入 mac 登录密码
3)重试
- 执行
./[compile-ffmpeg.sh](compile-ffmpeg.sh) all
报错
./libavutil/arm/asm.S:50:9: error: unknown directive
.arch armv7-a
^
make: *** [libavcodec/arm/aacpsdsp_neon.o] Error 1
报错原因是:因为xcode对32位的支持弱化了,需要进入 compile-ffmpeg.sh 里面修改脚本,正确修改后重新执行 ./[compile-ffmpeg.sh](compile-ffmpeg.sh) all
即可
compile-ffmpeg.sh 在这里
修改如下
- 正确修改应该不会遇到的问题:如果只是隐藏ios7而没有修改ios8那行的话,后续编译程序会遇到这个问题
./libavutil/arm/asm.S:50:9: error: unknown directive
,我觉得是没有正确的编译 ffmpeg,我遇到这个问题后,重新修改成上面👆这样,再重新编译就可以了
三 打包IJKMediaFramework.framework
- 其实有两种集成方法,一种是跟demo一样直接导入 IJKMediaPlayer.xcodeproj ;第二种集成方法是把 ijkplayer 打包成framework导入工程中使用. 下面👇介绍 怎么打包成 IJKMediaFramework.framework,真的是小白都很简单做到的
- 打开工程后,设置工程的 scheme 为release
- 设置完成后,分别选择真机和模拟器进行编译
遇到的问题,没有问题可以跳到下一步
-
有文件缺失,先查看 ffmpeg 文件夹下的库是否完整,或者是不是存在着的~~~~如果 compile-ffmpeg.sh 脚本没有设置正确,就会导致这个问题,请回去 修改 compile-ffmpeg.sh 脚本以及编译 ffmpeg 的那一步检查一下
-
ffmpeg 文件夹下的库完整,但是还是显示文件丢失
示例1:
根据报错的路径找到文件
直接打开,然后注释掉# include "armv7/avconfig.h"
如果是报以下这几个相关的缺失错误,都是类似的解决办法
- 如果还有文件缺失,也有可能是路径的问题,这个我没有遇到,贴个别人的经验,不过我没有亲测过哦 include“libavformat/avformat.h” file not found 错误
-
真机和模拟器编译之后,会多出两个文件夹,查看方法如下:
-
合并真机和模拟器的framework ,注意要合并的文件是这个
执行命令: lipo -create "真机版本路径" "模拟器版本路径" -output "合并后的文件路径"
tips: 合并后的文件路径记得把新文件的名字加上啊,不然很可能合成之后会变成 .lipo 之类的东西😳
-
替换 IJKMediaFramework,很重要
四 集成ijkplayer
导入 framework,直接将 IJKMediaFramework.framework拖入到工程中即可,注意记得勾选 Copy items if needed和 对应的 target
添加下列依赖到工程
libc++.tbd( 编译器选 gcc 的请导入 libstdc++.tbd)
libz.tbd
libbz2.tbd
AudioToolbox.framework
UIKit.framework
CoreGraphics.framework
AVFoundation.framework
CoreMedia.framework
CoreVideo.framework
MediaPlayer.framework
MobileCoreServices.framework
OpenGLES.framework
QuartzCore.framework
VideoToolbox.framework
五 简单的使用测试下
我是参考的 swift之IJKPlayer 的应用,(测试代码原文出处:https://blog.csdn.net/amberoot/article/details/79536817 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
)亲测可用,代码贴出来你们方便测下~
// 初始化变量
let ViewForPlayer = UIView()
var player: IJKFFMoviePlayerController!
///保存视频流地址的字符串
var videoStreaming: String!
//提示播放器的状态,放在播放器正中央
var videoTip = UILabel()
//播放器加载或缓存的时候可见并转动
var indicator = UIActivityIndicatorView()
//要重启播放器时,把播放器的view存放起来,适当时候删掉
var oldPlayerView = UIView()
// 初始化控件
func initView(cgRect: CGRect,url: String,text:String)
{
indicator.style = UIActivityIndicatorView.Style.white
videoTip.text = text
videoTip.textColor = UIColor.lightGray
videoStreaming = url
//添加ViewForPlayer到界面
ViewForPlayer.frame = cgRect
ViewForPlayer.backgroundColor = UIColor.darkText
self.contentView.addSubview(ViewForPlayer)
// ViewController.playerContainer.addSubview(ViewForPlayer)
//初始化播放器
initPlayer(Url: url)
}
func setLabelViewConstraints() {
//系统默认会给autoresizing 约束,要关闭autoresizing才能让自定义的约束生效,否则程序崩溃
videoTip.translatesAutoresizingMaskIntoConstraints = false
//添加约束:"哪个控件" 的 “什么属性“ "等于/大于/小于" “另一个控件” 的 “什么属性” 乘以 "多少" 加上 "多少"
let Constraint_centerX = NSLayoutConstraint(item: videoTip, attribute: .centerX, relatedBy: .equal, toItem: ViewForPlayer, attribute: .centerX, multiplier: 1, constant: 0)
let Constraint_centerY = NSLayoutConstraint(item: videoTip, attribute: .centerY, relatedBy: .equal, toItem: ViewForPlayer, attribute: .centerY, multiplier: 1, constant: 0)
let Constraint_width = NSLayoutConstraint(item: videoTip, attribute: .width, relatedBy: .lessThanOrEqual, toItem: ViewForPlayer, attribute: .width, multiplier: 1, constant: 0)
let Constraint_height = NSLayoutConstraint(item: videoTip, attribute: .height, relatedBy: .lessThanOrEqual, toItem: nil, attribute: .height, multiplier: 1, constant: 20)
NSLayoutConstraint.activate([Constraint_centerX,Constraint_centerY,Constraint_width,Constraint_height])
}
func setIndicatorViewConstraints() {
//系统默认会给autoresizing 约束,要关闭autoresizing才能让自定义的约束生效,否则程序崩溃
indicator.translatesAutoresizingMaskIntoConstraints = false
//添加约束:"哪个控件" 的 “什么属性“ "等于/大于/小于" “另一个控件” 的 “什么属性” 乘以 "多少" 加上 "多少"
let Constraint_centerX = NSLayoutConstraint(item: indicator, attribute: .centerX, relatedBy: .equal, toItem: ViewForPlayer, attribute: .centerX, multiplier: 1, constant: 0)
let Constraint_centerY = NSLayoutConstraint(item: indicator, attribute: .centerY, relatedBy: .equal, toItem: ViewForPlayer, attribute: .centerY, multiplier: 1, constant: 0)
let Constraint_width = NSLayoutConstraint(item: indicator, attribute: .width, relatedBy: .lessThanOrEqual, toItem: nil, attribute: .width, multiplier: 1, constant: 20)
let Constraint_height = NSLayoutConstraint(item: indicator, attribute: .height, relatedBy: .lessThanOrEqual, toItem: nil, attribute: .height, multiplier: 1, constant: 20)
NSLayoutConstraint.activate([Constraint_centerX,Constraint_centerY,Constraint_width,Constraint_height])
}
// 初始化并开启播放器
func initPlayer(Url: String) {
//防止重复打开播放器导致内存溢出
if player != nil {
player.shutdown()//关闭播放器
oldPlayerView = player.view
indicator.removeFromSuperview()
videoTip.removeFromSuperview()
player = nil
print("++++++++重启播放器+++++++")
}
videoStreaming = Url
let url = NSURL(string: Url)
//options是对数据的处理,videotoolbox解码,设置音频视频等属性,都要有这个数据
let options = IJKFFOptions.byDefault()
//设置解码方式: 0-软解码;1-硬解码
// options?.setPlayerOptionIntValue(1, forKey: "videotoolbox")
//开启硬解码
// options?.setPlayerOptionValue("1", forKey: "videotoolbox")
//设置播放器缓冲: 0-关闭缓冲;1-开启缓冲;默认是1
// options?.setPlayerOptionIntValue(1, forKey: "packet-buffering")
// 最大缓存是3000(3s),可以依据自己的需求修改
// options?.setPlayerOptionIntValue(1000, forKey: "max_cached_duration")
//设置自动转屏
// options?.setFormatOptionIntValue(0, forKey: "auto_convert")
//设置重连:0-关闭重连;1-开启重连;
// options?.setFormatOptionIntValue(1, forKey: "reconnect")
//如果使用rtsp协议,可以优先用tcp(默认udp)
// options?.setPlayerOptionValue("tcp", forKey: "rtsp_transport")
//开启环路滤波IJK_AVDISCARD(0比48清楚,但解码开销大,48基本没有开启环路滤波,清晰度低,解码开销小)
// options?.setCodecOptionIntValue(48, forKey: "skip_loop_filter")
//帧速率(fps)-可以改,确认非标准桢率会导致音画不同步,所以只能设定为15或者29.97
// options?.setPlayerOptionIntValue(29.97, forKey: "r")
// //设置音量大小,256为标准音量。要设置成两倍音量时则输入512,依此类推
// options?.setPlayerOptionIntValue(256, forKey: "vol")
//设置静音
options?.setPlayerOptionValue("1", forKey: "an")
// IJKFFMoviePlayerController.setLogLevel(IJKLogLevel(rawValue: 3))
// IJKFFMoviePlayerController.setLogReport(false)
//默认是false
// player.shouldShowHudView = true
//初始化播放器,播放在线视频或直播
player = IJKFFMoviePlayerController(contentURL: url as URL?, with: options)
//播放页面视图宽高自适应
let autoresize = UIView.AutoresizingMask.flexibleWidth.rawValue | UIView.AutoresizingMask.flexibleHeight.rawValue
player.view.autoresizingMask = UIView.AutoresizingMask(rawValue: autoresize)
//设置视频缓存大小,缓存大延迟大,缓存小延迟小
player.setPlayerOptionIntValue(100, forKey: "framedrop")
player.view.frame = self.ViewForPlayer.bounds
player.scalingMode = .aspectFit//缩放模式
player.shouldAutoplay = true //开启自动播放
// player.allowsMediaAirPlay = true
//player.view的父控件设置自动适应
//fatherView.autoresizesSubviews = true
self.ViewForPlayer.addSubview(player.view)
//添加indicatorView到ViewForPlayer
ViewForPlayer.addSubview(indicator)
setIndicatorViewConstraints()
//添加label到ViewForPlayer
ViewForPlayer.addSubview(videoTip)
setLabelViewConstraints()
/////////////////
self.player.currentPlaybackTime = 1
//self.player.playbackState
DispatchQueue.main.async {
//开启播放器
self.player.prepareToPlay()
}
videoTip.text = " "
indicator.startAnimating()
//注册ijk播放器的通知
initIJKNoti()
}
// 注册ijk播放器的通知
func initIJKNoti() {
//注册ijk播放器的通知
NotificationCenter.default.addObserver(self, selector: #selector(moviePlayBackFinish), name: NSNotification.Name.IJKMPMoviePlayerPlaybackDidFinish, object: player)
NotificationCenter.default.addObserver(self, selector: #selector(loadStateDidChange), name: NSNotification.Name.IJKMPMoviePlayerLoadStateDidChange, object: player)
NotificationCenter.default.addObserver(self, selector: #selector(moviePlayBackStateDidChange), name: NSNotification.Name.IJKMPMoviePlayerPlaybackStateDidChange, object: player)
NotificationCenter.default.addObserver(self, selector: #selector(mediaIsPreparedToPlayDidChange), name: NSNotification.Name.IJKMPMediaPlaybackIsPreparedToPlayDidChange, object: player)
}
// 接收播放器状态改变的通知
//视频播放结束
@objc func moviePlayBackFinish(notifycation:Notification) {
let playerN = notifycation.object as! IJKFFMoviePlayerController
if playerN.bufferingProgress == 0 {
print("视频加载失败")
}
let reason = notifycation.userInfo?[IJKMPMoviePlayerPlaybackDidFinishReasonUserInfoKey] as! Int
NSLog("--------------moviePlayBackFinish:\(reason)")
switch (reason) {
case 0://playbackEnded
indicator.startAnimating()
if !videoStreaming.isEmpty {
initPlayer(Url: videoStreaming)
}
break
case 2://userExited
break
case 1://playbackError
print("播放错误,需要重新播放:\(reason)")
videoTip.text = "获取视频失败"
indicator.stopAnimating()
break
default:
break
}
}
///加载状态改变了
@objc func loadStateDidChange(notifycation:Notification) {
let playerN = notifycation.object as! IJKFFMoviePlayerController
let loadState = player?.loadState.rawValue
NSLog("--------------加载状态loadStateDidChange:\(loadState!)")
// IJKMPMovieLoadState
if loadState == 3 {//开始播放视频
indicator.stopAnimating()
self.oldPlayerView.removeFromSuperview()
}else if loadState == 4 {//网络不好导致了暂停
indicator.startAnimating()
if !videoStreaming.isEmpty {
initPlayer(Url: videoStreaming)
}
}
switch self.player.loadState {
// 状态为缓冲几乎完成,可以连续播放
case IJKMPMovieLoadState.playthroughOK:
// videoTip.text = "加载状态为缓冲几乎完成,可以连续播放"
break
// 可以播放状态
case IJKMPMovieLoadState.playable:
// videoTip.text = "加载状态为可以播放状态"
break
// 缓冲中
case IJKMPMovieLoadState.stalled:
videoTip.text = "网络不好导致了暂停"
indicator.startAnimating()
break
default:
print("加载状态-loadStateDidChange: \(loadState!)")//加载状态未知
}
}
///准备播放的媒体改变了
@objc func mediaIsPreparedToPlayDidChange(notifycation:Notification) {
_ = notifycation.object as! IJKFFMoviePlayerController
NSLog("--------------mediaIsPreparedToPlayDidChange")
videoTip.text = "mediaIsPreparedToPlayDidChange"
}
///视频播放状态改变了
@objc func moviePlayBackStateDidChange(notifycation:Notification) {
let playerN = notifycation.object as! IJKFFMoviePlayerController
//播放 1 暂停2 播放完成 0
NSLog("--------------播放状态改变了:\(player?.playbackState.rawValue ?? 9)")
// videoTip.text = "播放状态改变了:\(player?.playbackState.rawValue ?? 9)"
switch player?.playbackState.rawValue ?? 9 {
case 0://停止
// initPlayer(Url: videoStreaming)
break
case 1://播放
break
case 2://暂停
// initPlayer(Url: videoStreaming)
break
case 4://播放
break
default:
break
}
}
总结:集成的过程挺简单的,主要的原理和其他应用我还没有摸透,希望能帮到大家,然后自己也要进步才行啊。
续篇(当我想要打包上架)
坑有千千万万个,你永远不知道你掉到哪里去了
ld: framework not found Pods_XXX clang: error: linker command failed with exit code 1 (use -v to see
- 解决方案:一般来说,是pod 的问题,删掉重新 pod install 就行。而我,是因为多target改了项目名~
clang: error: linker command failed with exit code 1 (use -v to see invocation)
- 解决方案:最快速的,Build Settings”->”Enable Bitcode” 设置为 NO
Undefined symbols for architecture armv7:
"_avio_open_dyn_buf", referenced from:
_vtbformat_init in IJKMediaFramework(IJKVideoToolBoxAsync.o)
_decode_video_internal in IJKMediaFramework(IJKVideoToolBoxAsync.o)
_vtbformat_init in IJKMediaFramework(IJKVideoToolBoxSync.o)
_decode_video_internal in IJKMediaFramework(IJKVideoToolBoxSync.o)
"_avio_close_dyn_buf", referenced from:
_vtbformat_init in IJKMediaFramework(IJKVideoToolBoxAsync.o)
_decode_video_internal in IJKMediaFramework(IJKVideoToolBoxAsync.o)
- 原因是使用ijkPlayer,生成ijkFramework时,删除了对armv7的支持。
- 解决方案: Build Settings --> Architectures --> Vaild Architectures 去掉 armv7。
希望没有后续~