iOS 튜토리얼 – 초간단 영상회의 서비스 만들어 보기

영상/음성 통화 iOS 앱 만들기

PlayRTC 클래스

version 2.2.0 기준

이번 튜토리얼에서 사용하게 되는 PlayRTC의 클래스로 각각 다음과 같은 기능을 합니다.

  1. PlayRTC
    • PlayRTC의 주요 기능과 P2P를 다루는 클래스
  2. PlayRTCFactory
    • PlayRTC 인스턴스를 생성하고 관리하기 위한 클래스
  3. PlayRTCSettings
    • PlayRTC의 각종 설정을 관리하는 클래스
  4. PlayRTCObserver
    • 채널에 접속할때, 연결이 끊어졌을때 등 각종 콜백 이벤트들이 지정된 인터페이스 클래스
  5. PlayRTCMedia
    • 비디오, 오디오 미디어 스트림을 다루는 클래스. 비디오를 표출하기 위해서는 PlayRTCVideoView가 지정되어야 함
  6. PlayRTCVideoView
    • 비디오를 표출하기위한 PlayRTC의 커스텀 뷰

PlayRTC Application 주요 개발 단계

PlayRTC 클래스는 PlayRTC SDK에서 가장 중요한 클래스 입니다. PlayRTC 클래스를 이용하여 P2P 연결에 필요한 각종 작업을 수행하고 명령을 내립니다. 또한 PlayRTCObserver 인터페이스를 구현한 클래스를 생성하여 이벤트에 따라 콜백 함수를 처리할 수 있습니다. 아래는 PlayRTC 클래스를 이용하여 작업하게 될 대부분의 일을 iOS의 앱의 각 단계별 시점에 따라 간단하게 정리하였습니다.

  1. PlayRTC 객체 생성 – viewDidLoad Time
    • 각종 콜백 이벤트 프로토콜인 PlayRTCObserver, 인스턴스 생성의 PlayRTCFactory클래스를 다루게 됩니다.
    • PlayRTCObserver 프로토콜의 구현
      • 이 프로토콜은 아래의 PlayRTC 인스턴스에 등록되어 “onConnectChannel”, “onDisconnectChannel”등의 이벤트에 콜백형태로 기능을 삽입할때 사용됩니다.
    • PlayRTCFactory를 통해 PlayRTC 인스턴스 생성
      • 이때 이전에 구현해 두었던 PlayRTCObserver 구현 인스턴스를 인자로 넘겨주어 실질적으로 콜백 함수를 등록하게 됩니다.
  2. PlayRTC 설정 – viewDidLoad Time
    • 각종 설정의 PlayRTConfig 객체를 다루게 됩니다.
    • PlayRTCConfig 객체를 통해 각종 설정
      • 기본적으로 T Developers의 Project Key를 설정합니다. 아래의 링크를 참고하여 내 앱을 위한 Project Key를 준비해 둡니다.
    • 그 외에 비디오, 오디오, 데이터 채널의 설정을 합니다.
  3. PlayRTCVideoView 생성 – viewWillAppear Time
    • 비디오 표출을 위한 PlayRTC의 커스텀 뷰인 PlayRTCVideoView 객체를 다루게 됩니다.
    • PlayRTCVideoView로 로컬 비디오 뷰(내 얼굴이 표출)를 생성 합니다.
    • PlayRTCVideoView로 리모트 비디오 뷰(상대방의 얼굴이 표출)를 생성 합니다.
  4. PlayRTC 플랫폼 채널 입장
    • PlayRTC의 connectChannel, createChannel 메소드를 다루게 됩니다.
    • layRTC 플랫폼 채널 서비스에 새로운 채널을 생성하고 입장을 하거나, 기존의 만들어진 채널에 입장하여 P2P연결 수립을 수행합니다. 한 사용자는 채널을 생성하여야 하고, 다음 사용자는 만들어진 채널의 아이디로 채널에 입장을 합니다.  PlayRTCObserver의 onConnectChannel 콜백 이벤트에 필요한 코드를 작성하게 됩니다.
  5. 채널 연결 수립 후 로컬미디어스트림 출력
    • 미디어 스트림을 위한 PlayRTCMedia 객체를 다루게 됩니다.
    • 채널 연결 수립 후 PlayRTCObserver의 onAddLocalStream 콜백을 통해 PlayRTCMedia 인스턴스를 전달받게 됩니다. 전달받은 PlayRTCMedia를 이전에 만들어둔 PlayRTCVideoView의 로컬 비디오 뷰에 지정하면 화면이 표출 됩니다.
  6. P2P 연결수립 후 원격미디어스트림 출력
    • 미디어 스트림을 위한 PlayRTCMedia 객체를 다루게 됩니다.
    • P2P 연결 수립 후 PlayRTCObserver의 onAddRemoteStream 콜백을 통해 PlayRTCMedia 인스턴스를 전달받게 됩니다. 전달 받은 PlayRTCMedia를 이전에 만들어둔 PlayRTCVideoView의 리모트 비디오 뷰에 지정하면 화면이 표출 됩니다.
  7. 상대방과의연결종료처리
    • PlayRTC의 disconnectChannel, deleteChannel 메소드와 PlayRTCObserver의 onDisconnectChannel, onDeleteChannel 콜백 이벤트를 다루게 됩니다.
    • PlayRTC의 disconnectChannel을 호출하여 채널에서 퇴장하거나 deleteChannel을 호출하여 채널을 종료하여 상대방과의 연결을 종료하고 필요에 따라 PlayRTCObserver의 적절한 콜백 이벤트에 필요한 사항을 처리합니다.

이제 이 흐름에 맞추어 구체적인 코드가 어떻게 구성되는지 알아보겠습니다.

PlayRTC 객체 생성

PlayRTCFactory 클래스의 createPlayRTC 메소드를 이용하여 PlayRTC 객체를 생성할 수 있습니다.

이때 SDK 설정을 위한 PlayRTCConfig 객체와 콜백 이벤트 처리를 위한 PlayRTCObserver 프로토콜을 구현한 인스턴스를 인수로 등록합니다.

PlayRTCConfig 객체를 통해 각종 설정

PlayRTCFactory의 createConfig() 메소드를 호출하여 PlayRTCConfig객체를 생성합니다.  설정 객체에 다음과 같은 값을 담고 있습니다.

  • PlayRTC 미디어 서비스 관련 설정
  • PlayRTC 로깅 설정 – 사용자 지정

이 값들을 필요에 따라 수정할 수 있는데, createChannel이나 connectChannel의 채널 접속 이전에 이 값을 수정하게 되면 수정된 값을 이용하여 채널에 접속하게 됩니다.

다음과 같은 형태로 구현할 수 있습니다.

- (settings*)setConfiguration {
    isClose = FALSE;

    PlayRTCConfig* settings = [PlayRTCFactory createConfig];

    [settings setProjectId:TDevelopersPorjectKey];
    
    [settings.video setEnable:TRUE];
    //PlayRTCDefine.h enum CameraType
    [settings.video setCameraType:CameraTypeFront];

    [settings.audio setEnable:TRUE];
    [settings.data setEnable:TRUE];

    [settings.log.console setLevel:LOG_TRACE];

    return settings;
}

PlayRTCObserver 인터페이스 구현

PlayRTCObserver에는 채널 접속, 끊김, 리모트 이벤트의 추가에 대한 다양한 이벤트 콜백이 담겨 있습니다. 이를 아래와 같이 구현하여 각 이벤트가 발생시에 필요한 코드를 작성할 수 있습니다.

-(void)onConnectChannel:(PlayRTC*)obj channelId:(NSString*)chId reason:(NSString*)reason {
}

-(void)onRing:(PlayRTC*)obj peerId:(NSString*)peerId peerUid:(NSString*)peerUid {
}

-(void)onAccept:(PlayRTC*)obj peerId:(NSString*)peerId peerUid:(NSString*)peerUid {  
}

-(void)onReject:(PlayRTC*)obj peerId:(NSString*)peerId peerUid:(NSString*)peerUid {  
}

-(void)onUserCommand:(PlayRTC*)obj peerId:(NSString*)peerId peerUid:(NSString*)peerUid data:(NSString*)data {
}

-(void)onAddLocalStream:(PlayRTC*)obj media:(PlayRTCMedia*)media {
}

-(void)onAddRemoteStream:(PlayRTC*)obj peerId:(NSString*)peerId peerUid:(NSString*)peerUid media:(PlayRTCMedia*)media {
}

-(void)onAddDataStream:(PlayRTC*)obj peerId:(NSString*)peerId peerUid:(NSString*)peerUid data:(PlayRTCData*)data {
}

-(void)onDisconnectChannel:(PlayRTC*)obj reason:(NSString*)reason {
}

-(void)onOtherDisconnectChannel:(PlayRTC*)obj peerId:(NSString*)peerId peerUid:(NSString*)peerUid {
}

-(void)onError:(PlayRTC*)obj status:(PlayRTCStatus)status code:(PlayRTCCode)code desc:(NSString*)desc {  
}

-(void)onStateChange:(PlayRTC*)obj peerId:(NSString*)peerId peerUid:(NSString*)peerUid status:(PlayRTCStatus)status desc:(NSString*)desc {    
}
func onConnectChannel(obj: PlayRTC!, channelId: String!, reason: String!) {
}
    
func onRing(obj: PlayRTC!, peerId: String!, peerUid: String!) {
}
    
func onAccept(obj: PlayRTC!, peerId: String!, peerUid: String!) {
}
    
func onReject(obj: PlayRTC!, peerId: String!, peerUid: String!) {
}
    
func onUserCommand(obj: PlayRTC!, peerId: String!, peerUid: String!, data: String!) {
}
    
func onAddLocalStream(obj: PlayRTC!, media: PlayRTCMedia!) {
}
    
func onAddRemoteStream(obj: PlayRTC!, peerId: String!, peerUid: String!, media: PlayRTCMedia!) {
}
    
func onAddDataStream(obj: PlayRTC!, peerId: String!, peerUid: String!, data: PlayRTCData!) {
}
    
func onDisconnectChannel(obj: PlayRTC!, reason: String!) {
}
    
func onOtherDisconnectChannel(obj: PlayRTC!, peerId: String!, peerUid: String!) {
}
    
func onError(obj: PlayRTC!, status: PlayRTCStatus, code: PlayRTCCode, desc: String!) {
}
    
func onStateChange(obj: PlayRTC!, peerId: String!, peerUid: String!, status: PlayRTCStatus, desc: String!) {
}

PlayRTCFactory로 PlayRTC 인스턴스 생성

구성해 두었던 PlayRTCObserver 인터페이스를 구현한 observer 인스턴스와 설정 객체 settings(PlayRTCConfig) 를 인수로 등록하여 새로운 인스턴스를 생성합니다.

self.playRTC = [PlayRTCFactory createPlatRTC:settings observer:(id<PlayRTCObserver>)self];

PlayRTCVideoView 생성

보통 화상통화 앱은 배경의 큰 화면에는 상대방의 화면이, 작은 화면에는 나의 모습이 보여지게 됩니다. 이 튜토리얼에서도 이런 형태로 구현 하도록 하겠습니다.

우선 화면배치을 위한 레이아웃을 만들고 이 레이아웃 안에서 상대방 화면인 remoteView와 나의 화면인 localView를 생성, 배치 합니다.

다음과 같은 형태로 구현할 수 있습니다.

// MainViewController.h
- (void)viewWillAppear:(BOOL)animated {
  [super viewWillAppear:animated];
  CGRect bounds = self.view.bounds;
  [self initScreenLayoutView:bounds];
}

#import "PlayRTCVideoView.h"

- (void) initScreenLayoutView:(CGRect)frame {
  CGRect bounds = frame;
  CGRect mainFrame = bounds;
  mainFrame.origin.y = 0;
  mainFrame.size.height = bounds.size.height;

  if([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
    mainFrame.origin.y += 30.0f;
    mainFrame.size.height -= 60.0f;
  }

  mainAreaView = [[UIView alloc] initWithFrame:mainFrame];
  mainAreaView.backgroundColor = [UIColor whiteColor];

  CGRect videoFrame = mainFrame;
  videoFrame.size.height = mainFrame.size.height;
  videoFrame.size.width = videoFrame.size.height / 0.75f;
  videoFrame.origin.y=0;

  videoAreaView = [[UIView alloc] initWithFrame:videoFrame];
  videoAreaView.center = mainAreaView.center;
  videoAreaView.backgroundColor = [UIColor brownColor];

  [self initVideoLayoutView:videoAreaView videoFrame:videoAreaView.bounds];
  [mainAreaView addSubview:videoAreaView];

  ... Something_Else ...
}

- (void) initVideoLayoutView:(UIView*)parent videoFrame:(CGRect)videoFrame {
  CGRect bounds = videoFrame;

  self.remoteVideoView = [[PlayRTCVideoView alloc] initWithFrame:bounds];

  CGRect localVideoFrame = videoFrame;
  localVideoFrame.size.width = localVideoFrame.size.width * 0.35;
  localVideoFrame.size.height = localVideoFrame.size.height * 0.35;
  localVideoFrame.origin.x = bounds.size.width - localVideoFrame.size.width - 10.0f;
  localVideoFrame.origin.y = 10.0f;

  self.localVideoView = [[PlayRTCVideoView alloc] initWithFrame:localVideoFrame];

  [parent addSubview:self.remoteVideoView];
  [parent addSubview:self.localVideoView];
}
override func viewWillAppear(animated: Bool) {
    var bounds: CGRect?

    super.viewWillAppear(animated)

    bounds = self.view.bounds

    // Make the videoView at the viewWillAppear time.
    initScreenLayoutView(bounds!)
}

func initScreenLayoutView(frame: CGRect) {
    var mainAreaView: UIView!
    var videoFrame: CGRect!
    var bounds: CGRect = frame
    var mainFrame: CGRect = bounds

    mainFrame.origin.y = 0
    mainFrame.size.height = bounds.size.height

    mainFrame.origin.y += 20.0
    mainFrame.size.height -= 20.0

    mainAreaView = UIView(frame: mainFrame)

    videoFrame = mainFrame

    videoAreaView = UIView(frame: videoFrame)
    videoAreaView!.backgroundColor = UIColor.brownColor()

    initVideoLayoutView(videoAreaView!, videoFrame: videoAreaView!.bounds)

    mainAreaView.addSubview(videoAreaView!)
}

func initVideoLayoutView(parent: UIView, videoFrame: CGRect) {
    var localVideoFrame: CGRect
    var bounds: CGRect = videoFrame

    self.remoteVideoView = PlayRTCVideoView.init(frame: bounds)

    localVideoFrame = videoFrame
    localVideoFrame.size.width = localVideoFrame.size.width * 0.35
    localVideoFrame.size.height = localVideoFrame.size.height * 0.35
    localVideoFrame.origin.x = bounds.size.width - localVideoFrame.size.width - 10.0
    localVideoFrame.origin.y = 10.0

    localVideoView = PlayRTCVideoView.init(frame: localVideoFrame)

    parent.addSubview(self.remoteVideoView!)
    parent.addSubview(self.localVideoView!)
}

PlayRTC 플랫폼 채널 입장

처음 채널에 입장히는 사용자가 채널을 생성하며 다른 사용자는 생성된 채널의 아이디로 채널에 입장을 하여 P2P연결을 합니다.

신규 채널을 생성하고 입장하기 – USER A

PlayRTC 플랫폼 채널 서비스에 채널을 새로 생성하고 입장하는 메소드는 PlayRTC::createChannel 입니다. 이 메소드를 호출하면 Peer A를 위한 채팅방이 PlayRTC 플렛폼 서버에 만들어지게 되며 채널 Id와 Peer A에게 할당된 토큰값등을 반환해줄 것입니다. 가장 기본적인 사용법은 아무런 입력값 없이 nil을 입력하여 createChannel(nil) 메소드 호출하여 채널을 생성합니다.

다음과 같은 형태로 구현할 수 있습니다.

- (void)createChannel:(NSString*)chName userId:(NSString*)userId {
    if(self.playRTC == nil) {
        return;
    }

    self.channelName = chName;

    if(self.channelName == nil) self.channelName = @"";

    NSMutableDictionary* parameters = [NSMutableDictionary dictionary];

    if(channelName != nil) {
        NSDictionary * channel = [NSDictionary dictionaryWithObject:chName forKey:@"channelName"];
        [parameters setObject:channel forKey:@"channel"];
    }

    if(userId != nil) {
        self.userUid = userId;
        NSDictionary * peer = [NSDictionary dictionaryWithObject:userId forKey:@"uid"];
        [parameters setObject:peer forKey:@"peer"];
    } else {
        self.userUid = @"";
    }

    [self.playRTC createChannel:parameters];
}
func createChannel(channelId: String?, userId: String?) {
    var parameters = [String: String]()

    if self.playRTC == nil {
        return
    }

    if channelId != nil {
        parameters.updateValue(channelId!, forKey: "channelId")
    }

    if userId != nil {
        self.userId = userId
        parameters.updateValue(userId!, forKey: "userId")
    }

    self.playRTC.createChannel(parameters)
}

만들어진 채널에 입장하기 – Peer B

이미 생성된 채널에 입장히는 메소드는 connectChannel 입니다.

connectChannel 호출 시 해당 채널 아이디가 필요합니다. 채널 사용자의 부가 정보를 같이 전달하여 채널/사용자 정보 조회 시 받아볼 수 있습니다. connectChannel가 잘 수행되면 onConnectChannel 메소드를 통해 보안등을 위한 채널 사용자 토큰정보등을 받을 수 있습니다.

다음과 같은 형태로 구현할 수 있습니다.

- (void)connectChannel:(NSString*)chId userId:(NSString*)userId {
    if(self.playRTC == nil) {
        return;
    }

    if(chId != nil) {
        self.channelId = chId;
    } else {
        self.channelId = @"";
    }

    NSMutableDictionary* parameters = [NSMutableDictionary dictionary];

    if(userId != nil) {
        self.userUid = userId;
        NSDictionary * peer = [NSDictionary dictionaryWithObject:userId forKey:@"uid"];
        [parameters setObject:peer forKey:@"peer"];
    }

    [self.playRTC connectChannel:self.channelId parameters:parameters];
}
func connectChannel(channelId: String?, userId: String?) {
    var parameters = [String: String]()

    if self.playRTC == nil {
        return
    }

    if channelId != nil {
        self.channelId = channelId
    }

    if channelId != nil {
        parameters.updateValue(channelId!, forKey: "channelId")
    }

    if userId != nil {
        parameters.updateValue(userId!, forKey: "userId")
    }

    self.playRTC.connectChannel(self.channelId, parameters: parameters)
}

PlayRTC 미디어 스트림 출력

PlayRTCMedia 객체는 미디어 스트림을 다루고 있으며, 이를 화면으로 표출하기 위해서 이전 단계에서 만들었던 뷰와 연결을 하여 실제 화면을 표출합니다.

로컬 미디어 스트림 출력 – PlayRTCObserver.onAddLocalStream

PlayRTCObserver 인터페이스의 콜백 이벤트 onAddLocalStream을 통해 미디어를 뷰로 표출토록 합니다. onAddLocalStream은 채널 연결이 성공적으로 수행된 이후 발생됩니다.

#import "PlayRTCMedia.h"

-(void)onAddLocalStream:(PlayRTC*)obj media:(PlayRTCMedia*)media {
  self.localMedia = media;
  [media setVideoRenderer:[self.localVideoView getVideoRenderer]];
}
func onAddLocalStream(obj: PlayRTC!, media: PlayRTCMedia!) {
    self.localMedia = media
    media.setVideoRenderer(self.localVideoView?.getVideoRenderer())
}

리모트 미디어 스트림 출력 – PLayRTCObserver.onAddRemoteStream

PlayRTCObserver 인터페이스의 콜백 이벤트 onAddRemoteStream을 통해 미디어를 뷰로 표출토록 합니다. onAddRemoteStream은 P2P연결이 성공적으로 수행된 이후 발생됩니다.

#import "PlayRTCMedia.h"

-(void)onAddRemoteStream:(PlayRTC*)obj peerId:(NSString*)peerId peerUid:(NSString*)peerUid media:(PlayRTCMedia*)media {
  self.remoteMedia = media;
  [media setVideoRenderer:[self.remoteVideoView getVideoRenderer]];
}
func onAddRemoteStream(obj: PlayRTC!, peerId: String!, peerUid: String!, media: PlayRTCMedia!) {
    self.remoteMedia = media
    media.setVideoRenderer(self.remoteVideoView?.getVideoRenderer())
}

PlayRTC 연결 종료

PlayRTC에서 상대방과 연결을 종료하는 경우는 다음의 경우가 있습니다.

  1. 상대방이 채널에서 퇴장 하는 경우 – 채널이 유효한 상태
    • 상대방이 disconnectChannel를 호출하면 SDK는 채널 서비스로부터 채널 퇴장 이벤트를 받아 채널에서 퇴장하며, 채널 서비스는 나에게 상대방의 채널 퇴장을 알리는 이벤트(onOtherDisconnectChannel)를 보내 줍니다.
  2. 자신이 채널에서 퇴장하는 경우 – 채널이 유효한 상태
    • disconnectChannel을 호출하면 SDK는 채널 서비스로부터 채널 퇴장 이벤트(onDisconnectChannel)를 받아 채널에서 퇴장하고, 채널 서비스는 상대방에게도 나의 채널 퇴장 이벤트(onOtherDisconnectChannel)를 전달하게 됩니다. 상대방은 이 이벤트를 받아 나의 채널 퇴장을 알게 됩니다.
  3. 상대방 또는 자신이 채널을 종료 하는 경우 – 예제에서의 채널 종료 방법
    • deleteChannel을 호출하면 채널 서비스는 채널에 입장한 모든 사용자에게 채널 종료를 통보(onDisconnectChannel)하고 채널에 있는 모든 사용자의 연결을 종료합니다. 이때 각각의 사용자는 종료하는 과정을 진행합니다.

채널 종료를 이용한 PlayRTC 연결 종료

화면의 Close 버튼을 이용하여 종료과정을 구현합니다. 우선 화면에 PlayRTC를 종료하는 버튼을 생성(MainViewController_UILayout)하고 클릭 이벤트 시 PlayRTC의 deleteChannel을 호출하여 채널 종료를요청합니다.

채널 서비스에서 채널 종료 이벤트(MainViewController_PlayRTC의 onDeleteChannel,)를 받으면 ViewController를 종료 하도록 MainViewController의 closeApp을 호출합니다.

다음과 같은 형태로 구현할 수 있습니다.

[self performSelector:@selector(deleteChannel) withObject:nil afterDelay:0.1];

-(void)onDisconnectChannel:(PlayRTC*)obj reason:(NSString*)reason {
  [self closeApp];
}

-(void)closeApp {
  UIViewController* viewController = [self.navigationController popViewControllerAnimated:TRUE];
  viewController = nil;

  // This needs a approval of the Apple App Store
  exit(0);
}

- (void)dealloc {
  self.localMedia = nil;
  self.remoteMedia = nil;
  self.channelId = nil;
  self.token = nil;
  self.userUid = nil;

  [self.remoteVideoView removeFromSuperview];
  self.remoteVideoView = nil;
  [self.localVideoView removeFromSuperview];
  self.localVideoView = nil;

  self.playRTC = nil;
}
 

Play RTC

서비스 체험

Play RTC build PlaygrOund

나만의 플레이그라운드를 만들어 친구를 초대해보세요 !

www.playrtc.com/

번거로운 가입이나 설치 없이ID만 만들어서 영상통화나 파일 공유, 채팅 서비스를 무료로 즐겨보세요.

 

Play RTC

서비스 체험

개설한 Playground 주소를 복하해서 친구에게 보내조세요. 친구가 Playground에 접속하면 이곳에 친구의 영상과 음성이 나타납니다. Waiting..

Photo

X
이미지 미리보기
이미지
 

서비스 체험

 

서비스 체험

서비스 체험은 크롬과 파이어폭스 브라우저에 최적화되어 있습니다.
크롬 또는 파이어폭스 브라우저를 설치 후 다시 이용해주세요.