ios LFLiveKit 라이브러리 이용해서 RTMP 구현 예제

ios LFLiveKit 라이브러리 이용해서 RTMP 구현 예제

 

ios 에서 rtmp 실시간 스트리밍을 구현하기 위해서 이곳에서 라이브러리를 받아주세요.

podfile에 추가하신 후 pod install 명령어로 설치를 해봅니다.

 

헤더파일 생성

위의 rtmp 라이브러리를 swift에서 사용하기 위해서 헤더 파일을 만들어 줍니다.

그리고 rtmp 라이브러리를 import 해줍니다.

#ifndef Bridging_Header_h
#define Bridging_Header_h


#import <LFLiveKit/LFLiveKit.h>

#endif /* Bridging_Header_h */

 

그리고 빌드셋팅으로 가셔서 Bridging Header 부분에 위 브릿지 헤더 파일 경로를 작성해 줍니다.

 

변수 선언

먼저 LFLiveSession 타입의 변수를 선언해줍니다.

    //MARK: 세션
    var session: LFLiveSession!

 

ViewDidLoad 작성

ViewDidLoad 부분에서 session을 설정하고 초기화 하는 로직을 작성해줍니다. 그리고 스트리밍 시작과 카메라 전환 버튼도 넣어줍니다.

 

    //MARK: viewDidLoad
    override func viewDidLoad() {
        super.viewDidLoad()

        self.view.backgroundColor = .white
        let screenW = UIScreen.main.bounds.size.width
        
        //MARK: 시작/종료버튼
        let playBtn = UIButton(frame: CGRect(x: (screenW - 100) / 2, y: 650, width: 100, height: 40))
        playBtn.backgroundColor = .black
        playBtn.setTitle("play", for: .normal)
        playBtn.setTitle("stop", for: .selected)
        playBtn.addTarget(self, action: #selector(playBtnOnClick(btn:)), for: .touchUpInside)
        view.addSubview(playBtn)
        
        //MARK: 카메라 전환 버튼
        let switchCameraBtn = UIButton(frame: CGRect(x: (screenW - 100) / 2, y: 700, width: 100, height: 40))
        switchCameraBtn.backgroundColor = .black
        switchCameraBtn.setTitle("카메라 전환", for: .normal)
        switchCameraBtn.addTarget(self, action: #selector(switchCameraBtnOnClick(btn:)), for: .touchUpInside)
        view.addSubview(switchCameraBtn)
        
        //설정값
        let audioConfiguration = LFLiveAudioConfiguration.default()
        let videoConfiguration = LFLiveVideoConfiguration.defaultConfiguration(for: LFLiveVideoQuality.low1, outputImageOrientation: .portrait)

        
        //MARK: 세션 초기화
        session = LFLiveSession(audioConfiguration: audioConfiguration, videoConfiguration: videoConfiguration)
        session.delegate = self
        session.preView = self.view
        session.captureDevicePosition = .back
     
        self.requestAccessForAudio()
        self.requestAccessForVideo()
    }

 

오디오 & 비디오 접근 권한 체크 함수

오디오와 비디오 접근 권한을 체크하는 함수를 작성해 줍니다. 승인이 되었다면 session.running = true 로 설정해줍니다.

    //MARK: 비디오 접근
    func requestAccessForVideo()  {
        print(":::: 비디오 접근권한 체크 :::: ")

        let state = AVCaptureDevice.authorizationStatus(for: .video)

        switch state {
        case .notDetermined:
            print(":::: 비디오 접근 notDetermined :::: ")
            
            AVCaptureDevice.requestAccess(for: .video, completionHandler: { (grandted) in
                guard grandted else { return }
                DispatchQueue.main.async {
                    self.session.running = true
                }
            })
        case.authorized:
            print(":::: 이미 비디오 접근 승인 얻었음 :::: ")
            DispatchQueue.main.async {
                self.session.running = true
            }
            break
            
        case.denied:
            print(":::: 비디오 접근 denied :::: ")
            break
            
        case.restricted:
            print(":::: 비디오 접근 restricted :::: ")
            break
            
        @unknown default:
            print("....")
        }
        
    }
    
    //MARK: 오디오 접근
    func requestAccessForAudio()  {
        print(":::: 오디오 접근권한 체크 :::: ")

        let state = AVCaptureDevice.authorizationStatus(for:.audio)
        switch state {
        case .notDetermined:
            print(":::: 오디오 접근 notDetermined :::: ")
            AVCaptureDevice.requestAccess(for: .audio, completionHandler: { (granted) in
                guard granted else{
                    return
                }
            })
        case.authorized:
            print(":::: 이미 오디오 접근 승인 얻었음 :::: ")
            break
            
        case.denied:
            print(":::: 오디오 접근 denied :::: ")
            break
            
        case.restricted:
            print(":::: 오디오 접근 restricted :::: ")
            break
            
        @unknown default:
            print("....")
        }
    }

 

LFLiveSessionDelegate 메소드

//MARK: LFLiveSessionDelegate
extension ViewController : LFLiveSessionDelegate {
    //MARK: - Callback
    func liveSession(_ session: LFLiveSession?, debugInfo: LFLiveDebug?){
        print(":::: debugInfo: \(String(describing: debugInfo?.currentBandwidth))")    }
    
    func liveSession(_ session: LFLiveSession?, errorCode: LFLiveSocketErrorCode){
        print(":::: errorCode : \(errorCode.rawValue)")
    }
    
    func liveSession(_ session: LFLiveSession?, liveStateDidChange state: LFLiveState){
        print(":::: liveStateDidChange: \(state.rawValue)")
        switch state {
        case .ready:
            print("준비")
        case .pending:
            print("연결중...")
        case .start:
            print("실시간 스트리밍 시작")
        case .refresh:
            print("리프레쉬...")
        case .stop:
            print("정지")
        case .error:
            print("오류...")
        @unknown default:
            print(".....")
        }
    }

}

 

rtmp 스트리밍 시작과 종료 함수

    //MARK: 스트리밍 시작
    func startLive() -> Void {
        print(":::: 라이브 시작 :::: ")
        let stream = LFLiveStreamInfo()
        //stream.url = "rtmp://나의 도메인/live/test"             //hls 방식으로 스트리밍 요청
        stream.url = "rtmp://나의 도메인/live_rtmp/test"          //rtmp 방식으로 스트리밍 요청
        session.startLive(stream)
    }
    
    func stopLive() -> Void {
        print(":::: 라이브 정지 :::: ")
        session.running = false
        session.stopLive()
    }

 

플레이버튼과 카메라 전환 버튼 구현

    //MARK: - 버튼 Event
    @objc func playBtnOnClick(btn: UIButton) {
        btn.isSelected = !btn.isSelected
        
        if btn.isSelected {
            print(":::: 시작 버튼 클릭됨 :::: ")
            startLive()

        } else {
            print(":::: 종료 버튼 해제됨 :::: ")
            stopLive()
        }
    }
    
    @objc func switchCameraBtnOnClick(btn: UIButton) {
        if session.captureDevicePosition == .back {
            session.captureDevicePosition = .front
        }else if session.captureDevicePosition == .front {
            session.captureDevicePosition = .back
        }
    }

 

테스트

위의 코드로 플레이 해보면, 현재 아이폰 화면에서 보여지는 화면이 해당 url의 rtmp 서버로 전송이되고, 다른 사람들이 팟 플레이어 등 rtmp 라이브 스트리밍 지원하는 플레이어에서도 시청을 할 수 있습니다.

 

 

전체 코드


import UIKit


class ViewController: UIViewController {

    //MARK: 세션
    var session: LFLiveSession!

    //MARK: viewDidLoad
    override func viewDidLoad() {
        super.viewDidLoad()

        self.view.backgroundColor = .white
        let screenW = UIScreen.main.bounds.size.width
        
        //MARK: 시작/종료버튼
        let playBtn = UIButton(frame: CGRect(x: (screenW - 100) / 2, y: 650, width: 100, height: 40))
        playBtn.backgroundColor = .black
        playBtn.setTitle("play", for: .normal)
        playBtn.setTitle("stop", for: .selected)
        playBtn.addTarget(self, action: #selector(playBtnOnClick(btn:)), for: .touchUpInside)
        view.addSubview(playBtn)
        
        //MARK: 카메라 전환 버튼
        let switchCameraBtn = UIButton(frame: CGRect(x: (screenW - 100) / 2, y: 700, width: 100, height: 40))
        switchCameraBtn.backgroundColor = .black
        switchCameraBtn.setTitle("카메라 전환", for: .normal)
        switchCameraBtn.addTarget(self, action: #selector(switchCameraBtnOnClick(btn:)), for: .touchUpInside)
        view.addSubview(switchCameraBtn)
        
        //설정값
        let audioConfiguration = LFLiveAudioConfiguration.default()
        let videoConfiguration = LFLiveVideoConfiguration.defaultConfiguration(for: LFLiveVideoQuality.low1, outputImageOrientation: .portrait)

        
        //MARK: 세션 초기화
        session = LFLiveSession(audioConfiguration: audioConfiguration, videoConfiguration: videoConfiguration)
        session.delegate = self
        session.preView = self.view
        session.captureDevicePosition = .back
     
        self.requestAccessForAudio()
        self.requestAccessForVideo()
    }

    
    //MARK: - 버튼 Event
    @objc func playBtnOnClick(btn: UIButton) {
        btn.isSelected = !btn.isSelected
        
        if btn.isSelected {
            print(":::: 시작 버튼 클릭됨 :::: ")
            startLive()

        } else {
            print(":::: 종료 버튼 해제됨 :::: ")
            stopLive()
        }
    }
    
    @objc func switchCameraBtnOnClick(btn: UIButton) {
        if session.captureDevicePosition == .back {
            session.captureDevicePosition = .front
        }else if session.captureDevicePosition == .front {
            session.captureDevicePosition = .back
        }
    }
    
    //MARK: 스트리밍 시작
    func startLive() -> Void {
        print(":::: 라이브 시작 :::: ")
        let stream = LFLiveStreamInfo()
        //stream.url = "rtmp://나의 도메인/live/test"             //hls 방식으로 스트리밍 요청
        stream.url = "rtmp://나의 도메인/live_rtmp/test"     //rtmp 방식으로 스트리밍 요청
        session.startLive(stream)
    }
    
    func stopLive() -> Void {
        print(":::: 라이브 정지 :::: ")
        session.running = false
        session.stopLive()
    }
    
    
    //MARK: 비디오 접근
    func requestAccessForVideo()  {
        print(":::: 비디오 접근권한 체크 :::: ")

        let state = AVCaptureDevice.authorizationStatus(for: .video)

        switch state {
        case .notDetermined:
            print(":::: 비디오 접근 notDetermined :::: ")
            
            AVCaptureDevice.requestAccess(for: .video, completionHandler: { (grandted) in
                guard grandted else { return }
                DispatchQueue.main.async {
                    self.session.running = true
                }
            })
        case.authorized:
            print(":::: 이미 비디오 접근 승인 얻었음 :::: ")
            DispatchQueue.main.async {
                self.session.running = true
            }
            break
            
        case.denied:
            print(":::: 비디오 접근 denied :::: ")
            break
            
        case.restricted:
            print(":::: 비디오 접근 restricted :::: ")
            break
            
        @unknown default:
            print("....")
        }
        
    }
    
    //MARK: 오디오 접근
    func requestAccessForAudio()  {
        print(":::: 오디오 접근권한 체크 :::: ")

        let state = AVCaptureDevice.authorizationStatus(for:.audio)
        switch state {
        case .notDetermined:
            print(":::: 오디오 접근 notDetermined :::: ")
            AVCaptureDevice.requestAccess(for: .audio, completionHandler: { (granted) in
                guard granted else{
                    return
                }
            })
        case.authorized:
            print(":::: 이미 오디오 접근 승인 얻었음 :::: ")
            break
            
        case.denied:
            print(":::: 오디오 접근 denied :::: ")
            break
            
        case.restricted:
            print(":::: 오디오 접근 restricted :::: ")
            break
            
        @unknown default:
            print("....")
        }
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        stopLive()
    }
}

//MARK: LFLiveSessionDelegate
extension ViewController : LFLiveSessionDelegate {
    //MARK: - Callback
    func liveSession(_ session: LFLiveSession?, debugInfo: LFLiveDebug?){
        print(":::: debugInfo: \(String(describing: debugInfo?.currentBandwidth))")    }
    
    func liveSession(_ session: LFLiveSession?, errorCode: LFLiveSocketErrorCode){
        print(":::: errorCode : \(errorCode.rawValue)")
    }
    
    func liveSession(_ session: LFLiveSession?, liveStateDidChange state: LFLiveState){
        print(":::: liveStateDidChange: \(state.rawValue)")
        switch state {
        case .ready:
            print("준비")
        case .pending:
            print("연결중...")
        case .start:
            print("실시간 스트리밍 시작")
        case .refresh:
            print("리프레쉬...")
        case .stop:
            print("정지")
        case .error:
            print("오류...")
        @unknown default:
            print(".....")
        }
    }

}