웹뷰 방송 리스트 + 네이티브 Play 연동

    웹뷰 방송 리스트 + 네이티브 Play 연동


    기사 요약

    하이브리드 앱을 연동할 때 방송 리스트까지 네이티브로 새로 만들 필요는 없습니다. 웹뷰의 플러그인 리스트를 그대로 쓰고, 방송을 클릭했을 때만 네이티브로 넘겨 재생하면 됩니다.

    웹뷰의 방송 리스트에서 campaignKey만 네이티브로 전달해 정식 플레이어를 재생하는 구조

    구현 방식 비교

    리스트도 네이티브로 구현

    리스트 UI를 네이티브로 직접 구현하고 항목마다 미리보기 화면을 호출합니다. 목록 구성·페이징·상태 갱신을 전부 직접 관리해야 합니다.

    웹뷰 리스트 재사용 (이 문서)

    리스트는 웹뷰 안에서 플러그인의 setOverall이 그대로 그립니다. 클릭된 방송의 키만 네이티브로 넘기고, 재생만 play()로 처리합니다.

    웹뷰와 네이티브는 별개 레이어입니다. accessKey는 네이티브가 이미 갖고 있으므로, 웹뷰에서 네이티브로 전달하는 값은 campaignKey 하나입니다.

    진행 순서

    1. 웹뷰 — 리스트를 그리고, 클릭 시 네이티브로 campaignKey를 넘긴다
    2. 네이티브 — 넘어온 값을 받는다
    3. 네이티브 — SDK로 재생한다

    1단계. 리스트 렌더링 & 클릭 시 네이티브로 넘기기

    리스트는 setOverall이 그립니다. 클릭 콜백은 네이티브 인터페이스가 없으면 자동으로 기본 웹 모달 재생으로 동작하므로, 웹과 하이브리드 앱 어디에 배포해도 안전합니다.

    iOS 웹뷰는 WKWebViewConfigurationallowsInlineMediaPlayback = true가 설정돼 있어야 인라인 재생이 됩니다. (아래 코드와 별개로, 웹뷰 자체 설정입니다.)

    JavaScript

    <script src="https://static.shoplive.cloud/shoplive.js"></script>
    <script>
      var accessKey = 'YOUR_ACCESS_KEY';
    
      var messageCallback = {
        ON_CLICK_CAMPAIGN_LIST_ITEM: function (payload) {
          var APP_INTERFACE = "ShopLiveAppInterface";
          var campaignKey = payload.campaignKey;
    
          if (window[APP_INTERFACE]) {
            // Android, Flutter — 둘 다 postMessage(String)만 지원
            window[APP_INTERFACE].postMessage(campaignKey);
          } else if (window.webkit?.messageHandlers?.[APP_INTERFACE]) {
            // iOS
            window.webkit.messageHandlers[APP_INTERFACE].postMessage(campaignKey);
          } else {
            // 네이티브 인터페이스가 없으면(일반 웹) 기본 웹 모달로
            cloud.shoplive.showFeaturedPlayerModal({ campaignKey: campaignKey });
          }
        }
      };
    
      cloud.shoplive.init({ accessKey: accessKey, messageCallback: messageCallback });
    </script>
    
    <div id="shoplive-overall"></div>
    <script defer>
      cloud.shoplive.setOverall('shoplive-overall');
    </script>

    2단계. 네이티브에서 받기

    위 코드가 부르는 인터페이스 이름(ShopLiveAppInterface)을 네이티브 쪽에 그대로 등록하면 됩니다. 이 등록 코드 자체는 Shoplive SDK 기능이 아니라 웹뷰가 표준으로 제공하는 JS↔네이티브 통신 방식입니다. campaignKey 문자열 하나만 오가므로 세 플랫폼 모두 별도 파싱 없이 그대로 받습니다.

    iOS는 userContentController.add()WKWebView 생성 전, 즉 이 configuration으로 웹뷰를 만들기 전에 호출해야 합니다.

    Kotlin · Android

    webView.addJavascriptInterface(ShopLiveAppInterface(activity), "ShopLiveAppInterface")
    
    class ShopLiveAppInterface(val activity: Activity) {
        @JavascriptInterface
        fun postMessage(campaignKey: String) {
            activity.runOnUiThread {
                /* 3단계: ShopLive.play() 호출 */
            }
        }
    }

    Swift · iOS

    configuration.userContentController.add(self, name: "ShopLiveAppInterface")
    
    func userContentController(_ controller: WKUserContentController,
                               didReceive message: WKScriptMessage) {
        guard message.name == "ShopLiveAppInterface",
              let campaignKey = message.body as? String else { return }
    
        // 3단계: ShopLive.play() 호출
    }

    Dart · Flutter

    controller.addJavaScriptChannel(
      'ShopLiveAppInterface',
      onMessageReceived: (JavaScriptMessage message) {
        final campaignKey = message.message;
    
        // 3단계: ShopLive.play() 호출
      },
    );

    이 브리지는 웹뷰 안의 어떤 JS든 호출할 수 있습니다. 웹뷰가 우리 페이지 외 다른 URL로 이동할 수 있다면(상품 링크 등), 그 페이지에서도 ShopLiveAppInterface를 호출할 수 있다는 뜻입니다. 외부 이동 시에는 새 창 또는 외부 브라우저로 열어서 웹뷰 안에는 우리 페이지만 남도록 하는 것을 권장합니다.

    3단계. 네이티브 재생

    브리지로 넘어온 campaignKey 하나면, 세 플랫폼 모두 같은 형태로 정식 플레이어를 띄웁니다. (게스트 재생 기준이며, 네이티브 SDK가 이미 accessKey로 초기화되어 있다는 전제입니다 — Android ShopLive.setAccessKey, iOS ShopLive.configure)

    Kotlin · Android

    ShopLive.play(activity, ShopLivePlayerData(campaignKey))

    Swift · iOS

    ShopLive.play(data: .init(campaignKey: campaignKey))

    Dart · Flutter

    shopLivePlayer.play(data: ShopLivePlayerData(campaignKey: campaignKey));

    로그인 사용자로 재생하기 (선택)

    게스트 재생이면 이 단계는 건너뛰어도 됩니다. 정할 것은 두 가지입니다 — 어떤 방식으로 인증할지, 그 값을 어디서 가져올지.

    인증 방식

    A · 보안 인증

    고객사 서버가 서명한 JWT로 검증합니다. setAuthToken

    B · 간편 인증

    서명·서버 검증 없이 userId만 전달합니다. setUser

    A · 보안 인증 (JWT)

    Kotlin · Android

    ShopLive.setAuthToken(jwt) // play() 호출 전

    Swift · iOS

    ShopLive.authToken = jwt

    Dart · Flutter

    shopLiveCommon.setAuthToken(userJWT: jwt);

    JWT는 Shoplive가 발급한 시크릿 키로 고객사 서버가 직접 서명해서 만듭니다. 웹뷰 플러그인에 쓴 JWT와 같은 사용자 기준으로 발급하면 됩니다.

    B · 간편 인증 (userId)

    Kotlin · Android

    ShopLive.setUser(ShopLiveCommonUser(userId))

    Swift · iOS

    ShopLiveCommon.setUser(user: ShopLiveCommonUser(userId: userId))

    Dart · Flutter

    shopLiveCommon.setUser(user: ShopLiveCommonUser(userId: userId));

    값의 출처

    위 인증 값을 play() 호출 전에 어디서 가져오는지는 하이브리드 앱의 로그인 구조에 따라 갈립니다.

    A · 네이티브가 로그인을 관리

    네이티브 앱이 이미 값을 갖고 있습니다. 브리지 payload는 그대로 두고, play() 직전에 이미 갖고 있는 값으로 위 API만 호출합니다.

    B · 웹뷰 세션에만 로그인이 있음

    네이티브는 사용자 정보를 모릅니다. 1단계 브리지 payload에 값을 함께 실어 보내고, 네이티브는 그 값을 받아 위 API를 호출합니다.

    B의 경우 문자열 하나로는 부족하므로 1단계 코드를 객체로 바꿔 값을 같이 실어 보내고, 2단계도 문자열이 아닌 JSON/객체를 파싱하도록 바꿔야 합니다. (아래는 JWT 기준 예시)

    JavaScript · 1단계 코드 확장

    var bridgePayload = { campaignKey: campaignKey, authToken: currentUserJWT };
    
    if (window[APP_INTERFACE]) {
      window[APP_INTERFACE].postMessage(JSON.stringify(bridgePayload));
    } else if (window.webkit?.messageHandlers?.[APP_INTERFACE]) {
      window.webkit.messageHandlers[APP_INTERFACE].postMessage(bridgePayload);
    }

    What's Next