Webview の配信リストとネイティブ Play の連携

    Webview の配信リストとネイティブ Play の連携


    記事の要約

    ハイブリッドアプリを連携する際、配信リストまでネイティブで作り直す必要はありません。Webview のプラグインリストをそのまま使い、配信をタップしたときだけネイティブに渡して再生します。

    Webview の配信リストから campaignKey のみをネイティブに渡し、正式プレーヤーを起動する構成

    実装方式の比較

    リストもネイティブで実装

    リスト UI をネイティブで実装し、項目ごとにプレビュー画面を呼び出します。リストの構成・ページング・状態更新をすべて自前で管理する必要があります。

    Webview のリストを再利用(本ガイド)

    リストは Webview 内でプラグインの setOverall がそのまま描画します。タップされた配信のキーだけをネイティブに渡し、再生のみ play() で処理します。

    Webview とネイティブは別レイヤーです。accessKey はネイティブが既に保持しているため、Webview からネイティブへ渡す値は campaignKey ひとつだけです。

    処理の流れ

    1. Webview — リストを描画し、タップ時にネイティブへ campaignKey を渡す
    2. ネイティブ — 渡された値を受け取る
    3. ネイティブ — SDK で再生する

    ステップ 1. リストの描画とタップ時のネイティブ連携

    リストは setOverall が描画します。クリックコールバックはネイティブインターフェースが無い場合、自動的に既定の Web モーダル再生として動作するため、Web でもハイブリッドアプリでも同じページを安全に配置できます。

    iOS の Webview では、インライン再生のために WKWebViewConfigurationallowsInlineMediaPlayback = true が必要です。(下記コードとは別の、Webview 自体の設定です。)

    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 {
            // ネイティブインターフェースが無い場合(通常の Web)は Web モーダルで
            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 の機能ではなく、Webview が標準で提供する JS↔ネイティブ通信の仕組みです。やり取りするのは campaignKey 文字列ひとつだけなので、3 プラットフォームとも解析なしでそのまま受け取れます。

    iOS では userContentController.add()WKWebView 生成前、つまりこの configuration で Webview を作成する前に呼び出す必要があります。

    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() を呼び出す
      },
    );

    このブリッジは Webview 内のどの JavaScript からも呼び出せます。Webview が自社ページ以外の URL(商品リンクなど)へ遷移し得る場合、そのページからも ShopLiveAppInterface を呼べるということです。外部遷移は新規ウィンドウまたは外部ブラウザで開き、Webview 内には自社ページのみが残る構成を推奨します。

    ステップ 3. ネイティブでの再生

    ブリッジで受け取った campaignKey ひとつで、3 プラットフォームとも同じ形で正式なプレーヤーを起動できます。(ゲスト再生の場合。ネイティブ 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));

    ログインユーザーとして再生する(任意)

    ゲスト再生であればこのステップは省略できます。決めることは 2 つです — どの方式で認証するかその値をどこから取得するか

    認証方式

    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 が発行したシークレットキーを使い、加盟店サーバーが自ら署名して生成します。Webview プラグインで使用した 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 · ログインが Webview セッションのみに存在

    ネイティブはユーザー情報を知りません。ステップ 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);
    }