본문 바로가기
아이폰 개발/ios 개념&튜토리얼

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

by 인생여희 2022. 9. 16.

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(".....")
        }
    }

}