Guides

iOS 연동 가이드

소스라이브 플레이어 iOS WebView 연동 가이드

라이브 URL로 접근했을 때 보여지는 페이지이며, 라이브를 시청할 수 있습니다.

라이브 전에는 편성표로 이동하고, 라이브가 시작되었다면 바로 플레이어로 이동합니다.

iOS WebView에 연동 방법

  • 로그인 페이지 URL과 소스라이브 페이지로 복귀하는 방식을 제공합니다.
  • WebView 에 Player URL을 추가합니다.

자사몰 로그인 페이지 URL

iOS 브릿지 통신 방법

프로젝트 설정

STEP 1. Xcode에서 새로운 프로젝트를 생성합니다.

STEP 2. WKWebView를 생성합니다.

웹 페이지 생성

간단한 웹 페이지 (index.html)를 만들어서 프로젝트에 추가합니다. 예제로 다음과 같은 웹 페이지를 사용합니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>JS Bridge Example</title>
</head>
<body>

<button onclick="sendMessageToiOS('Hello from Web!')">Send Message to iOS</button>

<script>
    function sendMessageToiOS(message) {
        window.webkit.messageHandlers.bridge.postMessage(message);
    }
</script>

</body>
</html>

WKWebView 설정

ViewController.swift에서 WKWebView를 설정해 웹 페이지를 로드하고 JavaScript와의 통신을 준비합니다.


import UIKit
import WebKit

class ViewController: UIViewController, WKScriptMessageHandler {
    
    @IBOutlet weak var webView: WKWebView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let contentController = WKUserContentController()
        contentController.add(self, name: "bridge")
        
        let config = WKWebViewConfiguration()
        config.userContentController = contentController
        
        webView.configuration.userContentController = contentController
        
        if let url = Bundle.main.url(forResource: "index", withExtension: "html") {
            webView.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent())
        }
    }
}

extension ViewController: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController,
                                    didReceive message: WKScriptMessage) {
        switch (message.name) {
            case "sauceflexEnter":   // 처음 플레이어 진입시
                print("sauceflexEnter")
                break
 
            case "sauceflexMoveExit":   // 닫기 버튼 눌러 팝업에서 나가기시
                print("sauceflexMoveExit")
                break
 
            case "sauceflexMoveLogin":   // 로그인 팝업에서 확인시
                print("sauceflexMoveLogin")
                break
 
            case "sauceflexMoveProduct":   // 상품 클릭시
                print("sauceflexMoveProduct")
                print(message.body)
                break
 
            case "sauceflexOnShare":   // 공유하기
                print("sauceflexOnShare")
                print(message.body)
                break
 
            case "sauceflexPictureInPicture":   // PIP 전환 버튼 클릭시, PIP 보기 전환시 보여주고 있는 컴포넌트 GONE 처리를 자동으로 처리합니다. (좋아요 버튼 등 플레이어 위에 보여지는 버튼들)
                print("sauceflexPictureInPicture")
                print(message.body)
                break
            
              // case "sauceflexPictureInPictureNoEvent":   // PIP 전환 버튼 클릭시, PIP 보기 전환시 보여주고 있는 플레이어 컴포넌트에 대해서는 직접적으로 GONE 처리 event 스크립트를 호출해야 합니다.
                //print("sauceflexPictureInPictureNoEvent")
                //print(message.body)
                //break
 
            // * PIP 버튼 브릿지 연동시에는 sauceflexPictureInPicture 또는 sauceflexPictureInPictureNoEvent 중 하나만 선언하여 사용해야 합니다.
 
            case "sauceflexMoveBanner":   // 배너 클릭시 bannerId (배너 고유 아이디), linkUrl (배너에 등록된 URL), broadcastIdx (방송 번호)
                print("sauceflexMoveBanner")
                print(message.body)
                break
 
            default:
                print("message.name \\(message.name) not handled.")
        }
    }
}

iOS PIP 모드 구현 방법

PIP 모드 소개

Apple의 기본 Picture-in-Picture(PIP) 기능과 플로팅 뷰를 구현하는 방법을 설명합니다. PIP 모드는 비디오 플레이어를 작은 화면으로 전환하여 다른 작업을 하면서도 영상을 계속 시청할 수 있게 하는 기능입니다. 플로팅 뷰는 이와 유사한 사용자 지정 뷰를 제공하며, 필요에 따라 더욱 다양한 사용자 경험을 제공합니다.

플로팅 커스텀뷰 구현

  • 앱 내 플레이어 실행 시 애플에서 제공해주는 PIP 기능과 유사하게 커스텀 된 플로팅뷰 기반으로 작동합니다.
  • 모비두 서비스에는 PIPKit이라는 오픈소스를 활용해 플로팅 기능과 PIP 기능을 구현했습니다.
    https://github.com/Kofktu/PIPKit
  • PIPKit은 아래 이미지를 참고해주세요.

플레이어 PIP 버튼 노출 설정 방법

플레이어 내에서 PIP 버튼을 노출하는 방법은 2가지가 있습니다.

컨피그(Config)로 설정

파트너사 설정 값에 모비두가 PIP 버튼을 노출하도록 설정하는 방법입니다.
파트너사별 Config 설정 (ex, Login URL 등록, Close 버튼 이동 URL 등록 등)을 할 때 PIP 버튼 노출을 요청합니다.

직접 설정

파트너사에서 직접 PIP 버튼 노출 처리를 할 수 있으며, 직접 관리하기 때문에 쉽게 설정할 수 있습니다.
(아래 설명 참조)

  • 앱과 Webview 간의 JavaScript 통신 방법 구현
    해당 Event 처리 시점은 sauceflexEnter 시점에 아래와 같이 PIP 버튼 노출을 처리합니다.
  • 웹뷰에 플레이어가 로드 되는 시간이 길어지는 경우 sauceflexEnter 시점에 javascript를 호출해도 적용이 안되는 경우가 발생할 수 있습니다. 해당 케이스의 경우 Javascript의 호출에 딜레이를 주어 적용을 확인합니다.
// PIP 사용 true 설정시 PIP 버튼 노출
userWebView?.evaluateJavascript( "(function() { window.dispatchEvent(sauceflexPictureInPictureUse(true)); })();") { }
// PIP 사용 false 설정시 PIP 버튼 미노출
userWebView?.evaluateJavascript( "(function() { window.dispatchEvent(sauceflexPictureInPictureUse(false)); })();") { }

PIP 모드 진입 방법

요구사항

  • iOS 15 이상
  • Info.plist: Audio, AirPlay and Picture in Picture 추가
  • PIPKit.isAVPIPKitSupported: Bool로 PIPKit 지원 여부 확인 가능

PIP 모드 활성화

  • 자세한 내용은 오픈소스 페이지를 참조하세요
  • AVPIPKitRenderer
protocol AVPIPKitRenderer {  
  var policy: AVPIPKitRenderPolicy { get }  
  var renderPublisher: AnyPublisher\<UIImage, Never> { get }

	func start()
	func stop()
}
  • AVPIPUIKitUsable 구현 예시
class View: UIView, AVPIPUIKitUsable {  
  var pipTargetView: UIView { self }
	var renderPolicy: AVPIPKitRenderPolicy {
    // .once: 한번만 렌더링
    // .preferredFramesPerSecond: 초당 프레임 렌더링
}
view.startPictureInPicture()  
view.stopPictureInPicture()
  • ViewController에 PIP 기능 추가
class ViewController: UIViewController, AVPIPUIKitUsable {  
    var pipTargetView: UIView { view }
		func start() {
 		   startPictureInPicture()
		}

		func stop() {
  		  stopPictureInPicture()
		}
}

PIP 모드 플레이어 UI 숨김 처리

sauceflexPictureInPicture 브릿지를 통해 PIP 모드에 진입하는 경우에는 자동으로 UI가 숨김 처리되지만, 이 외의 진입 시에는 UI 숨김 처리를 위한 Javascript 호출이 필요합니다.

func onPictureInPictureModeChanged(){
    if isActive == true {
        self.playerView.player?.pictureInPictureController?.stopPictureInPicture()
        myWebView?.evaluateJavascript( "(function() { window.dispatchEvent(sauceflexPictureInPictureOff); })();" ) { }
    } else {
        self.playerView.player?.pictureInPictureController?.canStartPictureInPictureAutomaticallyFromInline = true
        myWebView?.evaluateJavascript( "(function() { window.dispatchEvent(sauceflexPictureInPictureOn); })();" ) { }
    }
}

공유하기 기능 구현 방법

iOS 공유하기 기본 시스템 뷰 구현

linkUrl 사용

linkUrl 사용

shortUrl 사용

shortUrl 사용


sauceflexOnShare 브릿지를 통해 공유하기 기능 구현

extension ViewController: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController,
                               didReceive message: WKScriptMessage) {
        switch (message.name) {
        case "sauceflexOnShare":
            if let messageBody = message.body as? String,
               let messageData = messageBody.data(using: .utf8) {
                do {
                    if let parsedJSON = try JSONSerialization.jsonObject(with: messageData, options: []) as? [String: Any] {
                        var shareURL: URL?
                        
                        if let linkURLString = parsedJSON["linkUrl"] as? String {  // shortUrl 사용 시 parsedJSON["shortUrl"]으로 대체 가능
                            shareURL = URL(string: linkURLString)
                        }
                        
                        if let url = shareURL {
                            let itemsToShare = [url]
                            let activityViewController = UIActivityViewController(activityItems: itemsToShare, applicationActivities: nil)
                            activityViewController.popoverPresentationController?.sourceView = self.view
                            self.present(activityViewController, animated: true, completion: nil)
                        } else {
                            print("공유 가능한 URL이 없습니다.")
                        }
                    }
                } catch {
                    print("JSON 파싱 오류: \(error)")
                }
            }
        default:
            print(message.name)
        }
    }
}