iOS中集成ijkplayer视频直播框架 以及集成过程中遇到的问题

  • 因为项目里有查看监控的需要,是 rtmp格式的 的,看了相关的框架,最后决定用 b 站哒

  • 主要参照的是这个教程iOS中集成ijkplayer视频直播框架 ,虽然写得有点早,但是流程基本是一致的,我只是做一下笔记,以及补充一些自己遇到的问题,希望能帮到大家啦

真的就是,手把手教学哎

零 准备工作

需要 brew,yasm,pkg-config ,用一下命令查询即可

brew -v
git --version
yasm --version

我没有查,因为记得没装yasm ,就直接装了。brew一般都有装的了

brew install yasm

一 下载

  1. 下载地址 Ijkplayer

二 编译

  1. 终端进入到解压后的文件夹


    Snip20190830_17.png
  2. 执行命令 ./init-ios.sh 下载ffmpeg ,等待ing

  3. 执行命令 cd ios 进入ios 文件夹

  4. 编译 ffmpeg ,依次执行: ./compile-ffmpeg.sh clean./compile-ffmpeg.sh all 也是需要等一下,有点慢

遇到的问题,没问题请跳到下一步
  1. 执行````./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)重试
  1. 执行 ./[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 在这里


Snip20190830_18.png

修改如下


Snip20190830_19.png
  • 正确修改应该不会遇到的问题:如果只是隐藏ios7而没有修改ios8那行的话,后续编译程序会遇到这个问题 ./libavutil/arm/asm.S:50:9: error: unknown directive,我觉得是没有正确的编译 ffmpeg,我遇到这个问题后,重新修改成上面👆这样,再重新编译就可以了

三 打包IJKMediaFramework.framework

  • 其实有两种集成方法,一种是跟demo一样直接导入 IJKMediaPlayer.xcodeproj ;第二种集成方法是把 ijkplayer 打包成framework导入工程中使用. 下面👇介绍 怎么打包成 IJKMediaFramework.framework,真的是小白都很简单做到的
  1. 打开工程后,设置工程的 scheme 为release
Snip20190822_2.png
Snip20190822_4.png
Snip20190830_20.png
  1. 设置完成后,分别选择真机和模拟器进行编译
遇到的问题,没有问题可以跳到下一步
  • 有文件缺失,先查看 ffmpeg 文件夹下的库是否完整,或者是不是存在着的~~~~如果 compile-ffmpeg.sh 脚本没有设置正确,就会导致这个问题,请回去 修改 compile-ffmpeg.sh 脚本以及编译 ffmpeg 的那一步检查一下


    Snip20190830_21.png
  • ffmpeg 文件夹下的库完整,但是还是显示文件丢失
    示例1:


    1.jpeg

根据报错的路径找到文件


Snip20190830_23.png

直接打开,然后注释掉# include "armv7/avconfig.h"


Snip20190830_24.png

如果是报以下这几个相关的缺失错误,都是类似的解决办法
Snip20190830_26.png
  1. 真机和模拟器编译之后,会多出两个文件夹,查看方法如下:


    Snip20190830_31.png
Snip20190830_27.png
  1. 合并真机和模拟器的framework ,注意要合并的文件是这个


    Snip20190822_21.png

执行命令: lipo -create "真机版本路径" "模拟器版本路径" -output "合并后的文件路径"
tips: 合并后的文件路径记得把新文件的名字加上啊,不然很可能合成之后会变成 .lipo 之类的东西😳

  1. 替换 IJKMediaFramework,很重要


    Snip20190830_32.png

四 集成ijkplayer

  1. 导入 framework,直接将 IJKMediaFramework.framework拖入到工程中即可,注意记得勾选 Copy items if needed和 对应的 target

  2. 添加下列依赖到工程

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
Snip20190822_24.png

Snip20190822_22.png

五 简单的使用测试下

我是参考的 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)
WechatIMG727.jpeg
  • 原因是使用ijkPlayer,生成ijkFramework时,删除了对armv7的支持。
  • 解决方案: Build Settings --> Architectures --> Vaild Architectures 去掉 armv7。
WechatIMG729.jpeg

希望没有后续~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270