Shoplive Developers
한국어KOEnglishEN日本語JA

Shoplive SDK 連携ガイド

ライブコマース配信の視聴(Player)と配信(Streamer)をアプリに組み込む方法を、 実装したいケース(ミッション)単位で順番にたどれるようご案内します。
コード例は上部のiOS / Android タブで切り替えられます。

はじめに

概要 & 要件

Shoplive SDK は、ライブコマースに最適化された配信 SDK です。複雑なストリーミング処理は SDK が引き受け、アプリには視聴(Player)と配信(Streamer)を安定して組み込むための最小限のインターフェースだけを公開します。
ボタン一つでフルスクリーンのライブを開く、ホームフィードに小さなプレーヤーを埋め込む、PIP で見続ける、商品・クーポンを連携する、アプリから直接配信する — 必要な方式だけを選んで手軽に適用できます。

すぐに始める →

モジュール構成

ミッションマップ — 何を実装したいですか?

実装したいことミッション
ボタンをタップするとライブがフルスクリーンで開くミッション 1 · プレーヤーを直接表示する
プッシュ/SMS のリンクをタップするとすぐにライブが開くミッション 2 · ディープリンクで表示する
ログイン会員として視聴・チャットさせる (トークン/ハッシュ化/Secret Key)ミッション 3 · ユーザー情報の連携
ホームフィードやリストに配信のプレビューを入れるミッション 4 · 画面に埋め込む (プレビュー)
別の画面へ移動しても小さなウィンドウで見続けられるようにするミッション 5 · PIP の適用
商品タップ → 自社の詳細ページへ遷移させる / クーポンを発行するミッション 6 · イベント · 商品 · クーポン
色・フォントを変更する、または標準 UI をオフにして自社 UI を載せるミッション 7 · UI カスタマイズ & 配置
アプリから番組を配信するミッション 8 · 番組の配信

提供形態 (プラットフォーム対応)

形態iOSAndroid
Player · フルスクリーンShoplivePlayerViewController (present)ShoplivePlayer(context).start(…) (Activity)
Player · 埋め込みShoplivePlayerView (UIView)ShoplivePlayerView (FrameLayout)
Streamer · スタジオShopliveStreamerViewControllerShopliveStreamer(context).start(…)
参考 iOS のViewController 方式が Android のActivity 方式に対応します。概念・命名・イベントモデルは両プラットフォームで同型なので、一方のプラットフォームで覚えたコードはそのまま他方へ置き換えられます。

要件

項目iOSAndroidFlutter
最小 OSiOS 15.0Android 6.0 (minSdk 23)iOS 15.0 · Android 6.0 (minSdk 23)
開発ツールXcode 15 以上を推奨 (Swift 5.9)Android Gradle Plugin 8.x を推奨Flutter 3.22 以上 · Dart 3.4 以上 (Android: AGP 8.7 · JDK 17)
配布方式Swift Package ManagerMaven リポジトリ (sdk.shoplive.cloud)pub.dev (shoplive_player · shoplive_streamer)
現在のバージョン3.0.33.0.33.0.3
はじめに

インストール

現在の配布バージョンは iOS・Android・Flutter プラグインいずれも 3.0.3 です。対応環境は要件を参照してください。
// Xcode ▸ File ▸ Add Package Dependencies…
// https://github.com/shoplive/shoplive-sdk-ios   (Dependency Rule: Up to Next Major — 3.0.3)

// ターゲットには必要なライブラリだけを追加し、使うものだけを import します。
import ShoplivePlayerSDK     // 視聴 — ミッション 1〜7
import ShopliveStreamerSDK   // 配信 — ミッション 8 (配信を組み込む場合のみ)

// 共通(認証 · 設定 · ユーザー)の ShopliveCore は上記の SDK が一緒に取り込みます。
// Shoplive.initialize · setUser を使っても個別に import する必要はありません。
// settings.gradle.kts — Shoplive の Maven リポジトリを追加
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven { url = uri("https://sdk.shoplive.cloud/maven-repo") }
    }
}

// app/build.gradle.kts
dependencies {
    implementation("cloud.shoplive:shoplive-player-sdk:3.0.3")     // 視聴
    implementation("cloud.shoplive:shoplive-streamer-sdk:3.0.3")   // 配信
    // core は上記の SDK が自動的に取り込みます
}
# pubspec.yaml — Flutter 3.22 以上 · Dart 3.4 以上
dependencies:
  shoplive_player: 3.0.3     # 視聴(shoplive_core を含む)
  shoplive_streamer: 3.0.3   # 配信 — 必要な場合のみ

# ios/Podfile — iOS SDK は iOS 15 以上(pod install 時にフレームワークをダウンロード)
platform :ios, "15.0"
# Android: Shoplive の Maven リポジトリはプラグインが自動で追加します
Flutter Flutter プラグインは同じ 3.0.3 SDK を 3 つのパッケージで包みます — shoplive_player(視聴)、shoplive_streamer(配信)、shoplive_core(初期化 · ユーザー · コンバージョン、両方に含まれる)。3 つは同じバージョンを使います。すべての呼び出しは Future を返し、イベントとリクエストはデリゲートではなくストリームで届きます(ミッション 6)。フィードにプレーヤーを埋め込むウィジェットはないため、ミッション 4 の View 方式は使えません — ハイブリッド連携か PIP プレビューを使ってください。

権限 (Streamer 利用時)

  • iOS — Info.plist に NSCameraUsageDescription / NSMicrophoneUsageDescription を追加します。
  • Android — ランタイム権限(CAMERA / RECORD_AUDIO)はSDK が自ら要求します。
はじめに

クイックスタート (Quick Start)

インストールが完了したら、以下のコードで最初のライブを再生できます。

最初に用意する値 — 各キーの役割

値何かどこに渡すかいつ必要か
accessKey顧客企業(サービス)を識別するキー。Shoplive が顧客ごとに発行しますShoplive.initialize — アプリ起動時に 1 回常に — 初期化に必要な唯一の値
campaignKey再生する配信 1 件を指すキー。配信(キャンペーン)ごとに異なりますplay(campaignKey:) · start(campaignKey =)再生・配信のたび
ユーザー JWT視聴者が誰かを伝えるトークン。顧客企業のバックエンドが Secret Key で署名しますShoplive.setUser(.token(jwt:))ログインユーザーのとき(ミッション 3)— 未ログインなら省略
配信用トークン配信者の権限を証明するトークンShoplive.setStreamToken(_:)アプリから配信するときのみ(ミッション 8)
Secret KeyJWT の署名に使う秘密の値。Shoplive の担当者から受け取ります。アプリには入れません顧客企業のバックエンドセキュア認証を使うとき(ミッション 3 · ステップ 4)

accessKey がない場合は、Shoplive の担当者または Shoplive 管理画面で確認してください。Secret Key は管理画面では発行されず、Shoplive の担当者から受け取ります。

視聴 (Player) — 3 行

// 1) アプリ起動時に 1 回
Shoplive.initialize(.init(accessKey: "YOUR_ACCESS_KEY"))
// 2) ユーザーの識別 — ログインユーザーのみ (未ログインなら省略、ミッション 3)
Shoplive.setUser(.token(jwt: myJwt))
// 3) 再生
let player = ShoplivePlayerViewController(campaignKey: "YOUR_CAMPAIGN_KEY")
present(player, animated: true)
// 1) アプリ起動時に 1 回
Shoplive.initialize(context, ShopliveConfiguration("YOUR_ACCESS_KEY"))
// 2) ユーザーの識別 — ログインユーザーのみ (未ログインなら省略、ミッション 3)
Shoplive.setUser(ShopliveUser.Token(myJwt))
// 3) 再生 (インスタンス生成と同時に開始)
ShoplivePlayer(context).start(campaignKey = "YOUR_CAMPAIGN_KEY")
import "package:shoplive_player/shoplive_player.dart";

// 1) アプリ起動時に 1 回
await Shoplive.initialize(const ShopliveConfiguration(accessKey: "YOUR_ACCESS_KEY"));
// 2) ユーザー識別 — ログインユーザーのみ(ゲストは省略、ミッション 3)
await Shoplive.setUser(ShopliveUser.token(myJwt));
// 3) 再生
await ShoplivePlayer.instance.start("YOUR_CAMPAIGN_KEY");

この 3 行だけで、キャンペーンを読み込んで再生し、チャット・クーポン・商品オーバーレイまで動作します。 各行の詳細: ① 初期化設定 · ② ユーザー情報の連携 · ③ プレーヤーの表示。

順序に注意 再生の前に initialize が必ず先に実行されている必要があります。初期化は AppDelegate / Application.onCreate で行うことを推奨します。

配信 (Streamer)

Shoplive.initialize(.init(accessKey: "YOUR_ACCESS_KEY"))
Shoplive.setStreamToken(streamerJwt)          // 配信専用トークン (必須)

let studio = ShopliveStreamerViewController(campaignKey: "YOUR_CAMPAIGN_KEY")
present(studio, animated: true)
Shoplive.initialize(context, ShopliveConfiguration("YOUR_ACCESS_KEY"))
Shoplive.setStreamToken(streamerJwt)          // 配信専用トークン (必須)

ShopliveStreamer(context).start(campaignKey = "YOUR_CAMPAIGN_KEY")
import "package:shoplive_streamer/shoplive_streamer.dart";

await Shoplive.initialize(const ShopliveConfiguration(accessKey: "YOUR_ACCESS_KEY"));
await Shoplive.setStreamToken(streamerJwt);   // 配信専用トークン(必須)

await ShopliveStreamer.instance.start("YOUR_CAMPAIGN_KEY");

次のステップ画面用語ガイド · ミッション 1 から開始 →

画面 & デモ

画面用語ガイド

このセクションは画面の各要素の呼び名を整理したものです。以降のミッション・オプション・イベントの説明でも同じ用語を使います。 下記の番号は画面イメージの番号と 1:1 で対応します。LIVE限定 の表示は配信中のみ表示される要素です。

プレーヤー画面 (視聴)

プレーヤー画面の構成
番号要素表示条件説明 · 関連 API
①映像エリア常時ライブ/VOD の描画領域。resizeMode(fill/fit) — ミッション 1
②ブランドロゴ常時キャンペーン設定(コンソール)で指定
③キャンペーンタイトル常時campaignInfoReceived でも受信 — ミッション 6
④LIVE バッジ · 視聴者数 · いいねLIVE限定状態は campaignStatusChanged で受信 — ミッション 6
⑤チャットテキストオーバーレイLIVE限定映像上に流れるテキスト。入力フォント appearance.chatInputFont — ミッション 7
⑥商品カード(左下 · NOW)常時タップで ShoplivePlayerRequest.navigation(url:) — ミッション 6
⑦右側アイコン列(♡ · ? · 💬)常時標準 UI — いいね · 問い合わせ · チャット表示
⑧ミュートボタン常時isMuted プロパティと連動
⑨クーポンバブル運営者が配信したときタップで ShoplivePlayerRequest.coupon(id:respond:) — ミッション 6
⑩お知らせ(Notice)pill運営者が配信したとき運営者がコンソールで発行する流れるお知らせ
⑪イベント · お知らせポップアップ運営者が配信したとき運営者が発行。カスタムアクションでも受信 — ミッション 6
⑫閉じる · 最小化常時最小化 = in-App PIP に移行。enterPictureInPicture() — ミッション 5
配信前 / 配信中 / 終了後 キャンペーンのステータスが ready → live → ended(型辞典)へ変わると、LIVE限定 の要素が SDK によって自動的に表示・非表示されます。 アプリ側で個別にオン・オフする必要はなく、独自 UI を作る場合にのみ campaignStatusChanged(ミッション 6)を購読して自前でトグルします。
配信状態の比較

スタジオ画面 (配信)

スタジオ画面の構成
番号要素表示条件説明
①カメラプレビュー常時配信開始前から表示(全体背景)
②戻る · 閉じる常時左上。スタジオを終了
③接続状態 (Normal)常時右上。ネットワーク品質表示(ShopliveConnectionState に対応)
④ズーム倍率 (1x)常時タップして倍率を切り替え
⑤ズームスライダー常時ドラッグで連続ズーム
⑥サウンド · カメラ切替 · 設定常時右下の縦 3 つ — SDK 提供の配信ツール
⑦オーディオレベルメーター常時左下。マイク入力レベルの確認
⑧配信開始 · 終了常時下部中央。タップ → connecting → live。ShopliveStreamerEvent.stateChanged — ミッション 8

前へ ← クイックスタート次へ → デモアプリで確認する

画面 & デモ

デモアプリで確認する

SDK に同梱されるデモアプリは、本ガイドのミッションと同じケースメニュー(ミッションマップ)で構成されています。 コードを書く前にデモアプリで動作を確認すると、実装の理解が早まります。

  1. 標準モード — Shoplive デモ設定: インストール直後、入力なしですべての機能(全ミッション)をデモキャンペーンで確認できます。
  2. 自社情報モード — 導入企業の設定を入力: 設定画面で発行済みの accessKey · campaignKey · (任意)ユーザー JWT(発行方法)を入力すると、 自社のキャンペーンと自社のユーザーで同じケースを体験できます。
  3. 各ケース画面には本ガイドの該当ミッション番号が表示されるため、デモで見た動作 → ガイドのコードへそのままつながります。
デモアプリの配布チャネル(TestFlight / APK)とデモキャンペーンキーは、SDK 提供時にあわせてご案内します。

前へ ← 画面用語ガイド次へ → ミッション 1 · プレーヤーを直接表示する

ミッション · Player

ミッション 1 · プレーヤーを直接表示する

目標ボタンタップ → ライブがフルスクリーンで開き、チャット・商品まで動作する
事前準備インストール完了 · accessKey · campaignKey (発行案内)
  1. アプリ起動時に Shoplive.initialize が呼ばれているか確認します (クイックスタート)。
  2. 視聴への入口ボタンのアクションでプレーヤーを生成して表示します。
  3. (任意)デリゲートを接続してイベントを受け取ります — ミッション 6。
@IBAction func watchLive() {
    let player = ShoplivePlayerViewController(campaignKey: "YOUR_CAMPAIGN_KEY")
    player.delegate = self               // 任意: イベント受信
    present(player, animated: true)
}
fun watchLive() {
    ShoplivePlayer(context).apply {
        delegate = myDelegate            // 任意: イベント受信
    }.start(campaignKey = "YOUR_CAMPAIGN_KEY")
}
Future<void> watchLive() async {
  final player = ShoplivePlayer.instance;
  player.events.listen(onPlayerEvent);   // 任意: イベントを受け取る(ミッション 6)
  await player.start("YOUR_CAMPAIGN_KEY");
}
画面遷移を自分で制御したい場合 (Android) ShoplivePlayer(context).intent(campaignKey = …) が Intent を返します。自分で startActivity で表示すれば OK です。

完了確認

  • ボタンタップから 2〜3 秒以内に映像が再生される
  • 配信中のキャンペーンであれば、チャット・ライブバッジが表示される (画面用語 ②③)
  • 再生失敗時に error イベントの code/message がログに出力される (エラー処理)

関連: PIP · イベント次へ → ミッション 2 · ディープリンクで表示する

ミッション · Player

ミッション 2 · ディープリンクでプレーヤーを表示する

目標プッシュ/SMS/Web の myapp://live?campaign=XXXX リンクをタップ → アプリが開いてすぐ再生
事前準備ミッション 1 完了 · アプリの URL スキーム(または App Links/Universal Links)の設定

SDK に専用のディープリンク API があるわけではなく、ディープリンクから campaignKey をパースして ミッション 1 と同じように再生します。

  1. OS/フレームワークのディープリンクハンドラで URL を受け取ります。
  2. クエリから campaignKey(必要に応じて referrer — ShoplivePlayOptions)を取り出します。
  3. 初期化・ユーザー設定(ミッション 3)の完了を確認した上でプレーヤーを表示します。
// SceneDelegate
func scene(_ scene: UIScene, openURLContexts ctxs: Set<UIOpenURLContext>) {
    guard let url = ctxs.first?.url,
          let comps = URLComponents(url: url, resolvingAgainstBaseURL: false),
          let key = comps.queryItems?.first(where: { $0.name == "campaign" })?.value
    else { return }

    let referrer = comps.queryItems?.first(where: { $0.name == "ref" })?.value
    let player = ShoplivePlayerViewController(
        campaignKey: key,
        options: .init(referrer: referrer))
    topViewController?.present(player, animated: true)
}
// ディープリンク受信 Activity
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val data: Uri? = intent?.data           // myapp://live?campaign=XXXX
    val key = data?.getQueryParameter("campaign") ?: return
    val ref = data.getQueryParameter("ref")

    ShoplivePlayer(this).start(
        campaignKey = key,
        options = ShoplivePlayOptions(referrer = ref))
}
// アプリが受け取ったディープリンク URI で呼び出します(例: app_links · go_router)
Future<void> openLive(Uri uri) async {
  final key = uri.queryParameters["campaign"];   // myapp://live?campaign=XXXX
  if (key == null) return;
  final ref = uri.queryParameters["ref"];

  await ShoplivePlayer.instance.start(key,
      playOptions: ShoplivePlayOptions(referrer: ref));
}
コールドスタートに注意 ディープリンクではアプリがコールドスタートする場合があります。Shoplive.initialize・setUser が再生より先に完了するか確認してください。 初期化を AppDelegate/Application に置くと安全です (クイックスタート)。

完了確認

  • アプリが終了した状態(コールドスタート)でリンクをタップ → 再生までつながる
  • アプリが起動中(ウォーム)の状態でも同じように動作する
  • referrer がキャンペーンの流入統計に反映される (ShoplivePlayOptions)

前へ ← ミッション 1次へ → ミッション 3 · ユーザー情報の連携

ミッション · Player

ミッション 3 · ユーザー情報の連携

目標自社サービスの会員として視聴・チャット・統計が記録されるようにする
事前準備ミッション 1 完了 · (セキュア認証の場合)バックエンドでの JWT 発行準備 (ステップ 4)
setUser はユーザー情報を渡すもので、ユーザーを認証するものではありません ログイン(ID・パスワード、ソーシャルログインなど)はすべてアプリ側で行います。setUser はいまの視聴者が誰かを Shoplive に伝えるだけで、チャット・視聴・統計がその会員に紐づきます。Shoplive はユーザーの資格情報を確認しません。token では、その情報が加盟店サーバーが発行し改ざんされていないか(署名)を Shoplive が確認でき、profile ではその確認なしに情報をそのまま渡します。

ステップ 1 · ログイン状態で選ぶ — 推奨の設定

ユーザー推奨理由
未ログイン何も呼び出さないゲスト識別子はサーバーが発行します。guest は何もしないため、ログインしていたユーザーをゲストに戻すときは logout() を使います
ログイン (推奨)token — サーバーが署名した JWT改ざんできないため、会員ランク別の入場制限・購入連携・正確な視聴者統計に使えます
ログイン(バックエンド作業が難しい場合)profile — id・名前などを直接渡すSDK が署名なしの JWT を作ります(シンプル認証)。識別・統計には十分ですが、権限の検証には使わないでください
ログアウト・ユーザー情報のリセットShoplive.logout()設定したユーザー情報(JWT・プロフィール)を消去し、配信用トークンもあわせて消去します。accessKey・ゲスト識別子・UTM は保持されます。ユーザー情報だけを消去する呼び出しはありません(guest は何もしません)
推奨の流れ アプリ起動(未ログイン)→ 何もしない → ログイン中:再生のたびに直前でバックエンドから新しく発行した JWT を受け取り setUser(.token(jwt:)) → ログアウト時に logout() でユーザー情報をリセット。 変更したユーザー情報は次の再生から反映されます。ホームのプレビュー(ミッション 4)は未ログインのまま見せ、本視聴に入るときにログインを促すなど、画面ごとに使い分けても構いません。

ステップ 2 · setUser の呼び出し

// ① ログイン · セキュア認証 (推奨) — バックエンドが署名した JWT (ステップ 4)
Shoplive.setUser(.token(jwt: myJwt))

// ② ログイン · シンプル認証 — プロフィールを直接渡す (SDK が署名なしの JWT を作る)
Shoplive.setUser(.profile(
    id: hashedUserId,            // 生の ID ではなくハッシュを推奨 (ステップ 3)
    name: "ライブファン", age: 27, gender: .female,
    rank: 10))                    // ユーザーランク (-100 〜 100)

// ③ 未ログイン — 呼び出す必要はありません

// ログアウト — ユーザー情報(JWT・プロフィール)を消去し、配信用トークンもあわせて消去(accessKey・ゲスト識別子・UTM は保持)
Shoplive.logout()
// ① ログイン · セキュア認証 (推奨) — バックエンドが署名した JWT (ステップ 4)
Shoplive.setUser(ShopliveUser.Token(myJwt))

// ② ログイン · シンプル認証 — プロフィールを直接渡す (SDK が署名なしの JWT を作る)
Shoplive.setUser(ShopliveUser.Profile(
    id = hashedUserId,           // 生の ID ではなくハッシュを推奨 (ステップ 3)
    name = "ライブファン", age = 27, gender = ShopliveGender.FEMALE,
    rank = 10))                    // ユーザーランク (-100 〜 100)

// ③ 未ログイン — 呼び出す必要はありません

// ログアウト — ユーザー情報(JWT・プロフィール)を消去し、配信用トークンもあわせて消去(accessKey・ゲスト識別子・UTM は保持)
Shoplive.logout()
// ① ログイン · セキュア認証(推奨)— バックエンドが署名した JWT(ステップ 4)
await Shoplive.setUser(ShopliveUser.token(myJwt));

// ② ログイン · 簡易認証 — プロフィールを直接渡す(SDK が署名なしの JWT を生成)
await Shoplive.setUser(ShopliveUser.profile(
    id: hashedUserId,            // 元の ID よりハッシュを推奨(ステップ 3)
    name: "LiveFan", age: 27, gender: ShopliveGender.female,
    rank: 10));                    // ユーザー等級(-100〜100)

// ③ 未ログイン — 呼び出し不要

// ログアウト — ユーザー情報・トークンを消去
await Shoplive.logout();

ステップ 3 · ID のハッシュ化 — 会員 ID の平文を露出させない

内部の会員 ID をそのまま渡したくない場合は、アプリとバックエンドが同一の方式でハッシュ化した値を id として使用します。 同じユーザーには常に同じ値が出るよう、一貫した salt・アルゴリズムを使うことが重要です(統計・再訪の識別)。

ハッシュ化のコード例を見る (Swift · Kotlin · Dart)
import CryptoKit

func hashUserId(_ raw: String) -> String {
    let salted = raw + "APP_SALT"
    let digest = SHA256.hash(data: Data(salted.utf8))
    return digest.map { String(format: "%02x", $0) }.joined()
}
import java.security.MessageDigest

fun hashUserId(raw: String): String {
    val salted = raw + "APP_SALT"
    return MessageDigest.getInstance("SHA-256")
        .digest(salted.toByteArray())
        .joinToString("") { "%02x".format(it) }
}
import "dart:convert";
import "package:crypto/crypto.dart";   // pub: crypto

String hashUserId(String raw) {
  final salted = raw + "APP_SALT";
  return sha256.convert(utf8.encode(salted)).toString();
}
セキュリティに関する注意 ハッシュ化は平文の隠蔽にすぎず、認証ではありません。視聴権限の検証が必要な場合は必ずサーバー署名の JWT(token)を使用してください。salt のポリシーは Shoplive チームと合意した値を推奨します。

ステップ 4 · セキュア認証用の JWT を作る(顧客企業のバックエンド)

token 認証に使う JWT は、Shoplive が発行した Secret Key で顧客企業のバックエンドが署名します。アプリはログイン直後にバックエンドから JWT を受け取り、setUser(.token(jwt:)) に渡すだけです。

  1. Shoplive の担当者から Secret Key を受け取ります — base64 でエンコードされた文字列です
  2. ログインしたユーザー情報で下記の payload を作り、HS256 で署名します
  3. アプリから要求されるたびに(再生の直前ごとに)JWT を新しく発行して返します。アプリに JWT を保存して使い回さないでください
JWT は毎回新しく生成して使います 端末に JWT をキャッシュしたり、古いトークンを使い回したりしないでください。前回の発行以降にユーザー情報(名前・等級・タイプ)が変わっている可能性があり、漏えいしたトークンは exp まで使えてしまいます。アプリが setUser を呼ぶたびにバックエンドへ新しい JWT を要求してください。
claim必須説明例
exp必須有効期限 (Unix 秒)1516239022
userId必須ログインユーザーの一意な値(最大 255 バイト)。ハッシュ値でも構いません(ステップ 3)sample_user_id
iat発行時刻 (Unix 秒)1516195822
nameチャットに表示する名前。禁止語はアスタリスク(*)に置き換えられますライブファン
gender性別 — m · f · 空(未指定)f
age年齢。統計では 5 歳単位でまとめて表示されます27
userScoreユーザーランク(-100 〜 100)。ランク別の入場・抽選制限の基準10
userTypeユーザー種別。入場制限(Entry Type)を設定した配信で特定の種別だけを入場させるときVIP
profileチャットのプロフィール画像 URL (64×64px 推奨)https://…/profile.png
custom任意のデータ(最大 1KB){"grade":"vip"}

署名の例です。2 つのコードは同じトークンを作ります — Secret Key が base64 文字列である点だけ注意してください。

// Node.js — npm i jsonwebtoken
const jwt = require("jsonwebtoken");

const secret = Buffer.from(process.env.SHOPLIVE_SECRET_KEY, "base64");  // base64 をデコードしてバイト列で使う
const token = jwt.sign(
  { userId: hashedUserId, name: "ライブファン", gender: "f", age: 27, userScore: 10 },
  secret,
  { algorithm: "HS256", expiresIn: "12h" }   // exp · iat を自動で埋める
);
// Java — io.jsonwebtoken (jjwt)
String jwt = Jwts.builder()
    .signWith(SignatureAlgorithm.HS256, base64EncodedSecretKey)   // base64 文字列をそのまま渡す
    .setExpiration(new Date(System.currentTimeMillis() + 12 * 60 * 60 * 1000L))
    .setIssuedAt(new Date())
    .claim("userId", hashedUserId)
    .claim("name", "ライブファン")
    .claim("gender", "f")
    .claim("age", 27)
    .claim("userScore", 10)
    .compact();
Secret Key はバックエンドだけに アプリのコードやバンドルに入れると、誰でも取り出して他のユーザーになりすましたトークンを作れます。シンプル認証(profile)の JWT は署名がないため、権限の検証には使えません。

完了確認

  • ログイン後の視聴時に、チャットのニックネームが会員名で表示される
  • Shoplive 管理画面 の統計で該当ユーザー ID(ハッシュ)が照会できる
  • ログアウト → Shoplive.logout() でユーザー情報をリセットしたあとはゲストとして視聴される

前へ ← ミッション 2次へ → ミッション 4 · 画面に埋め込む (プレビュー)

ミッション · Player

ミッション 4 · 画面に埋め込む (プレビュー)

目標ホームフィードやリストに配信のプレビューを入れ、タップしたら本視聴を開く
事前準備ミッション 1 完了

フィードやリストに入れるプレーヤーはプレビューとして表示します。サーバーが提供する低解像度ストリームを受け取るため、1 画面に複数並べてもトラフィックが少なく、ミュートで流し見してタップすれば本視聴(ミッション 1)に移ります。画面を何で作っているかに応じて、2 つの方式のどちらかを選んでください。

方式いつプレビューを描く側タップしたとき
View 方式(ネイティブ)フィード・リストがネイティブ画面SDK の ShoplivePlayerView + type = .previewネイティブのフルスクリーンプレーヤーで開く
ハイブリッド連携(Web + Mobile SDK)フィード・リストが Web(WebView)Web ページが Web SDK で描くWeb がアプリに campaignKey を渡し、アプリがネイティブプレーヤーで開く

View 方式 — ネイティブのプレビュー

// プレビュープリセット — 低解像度(type = .preview)・ミュート・オーバーレイ非表示・in-App PIP 設定をまとめて
let playerView = ShoplivePlayerView(configuration: .preview)
container.addSubview(playerView)       // Auto Layout はホストアプリの自由
playerView.delegate = self
playerView.play(campaignKey: "YOUR_CAMPAIGN_KEY")
val playerView = ShoplivePlayerView(context)   // XML/Compose(AndroidView) の配置は自由
// プレビュープリセット — 低解像度(PREVIEW)・ミュート・オーバーレイ非表示・in-App PIP 設定をまとめて
playerView.configuration = ShoplivePlayerConfiguration.PREVIEW
container.addView(playerView)

playerView.delegate = myDelegate
playerView.bindLifecycle(viewLifecycleOwner)   // pause/resume/release を自動化
playerView.play(campaignKey = "YOUR_CAMPAIGN_KEY")
// Flutter 3.0.3 にもフィードに埋め込むウィジェットはありません(ShoplivePlayerView なし)。
// 代替: プレビューストリームを小さなアプリ内 PIP ウィンドウで開始
await ShoplivePlayer.instance.start("YOUR_CAMPAIGN_KEY",
    playOptions: const ShoplivePlayOptions(presentationStyle: ShoplivePresentationStyle.pip),
    configuration: const ShoplivePlayerConfiguration(
        type: ShoplivePlayerType.preview,                            // プレビューストリーム(低解像度)
        sound: ShopliveSoundOptions(muteOnStart: true),              // ミュートで開始
        overlay: ShopliveOverlayOptions(ui: ShopliveOverlayUIMode.hidden),   // オーバーレイを非表示
        pip: ShoplivePipOptions(isInAppPipEnabled: true)));          // presentationStyle: pip に必要

プレビュー設定 — 何が変わるか

設定効果
type = .preview
(Android ShoplivePlayerType.PREVIEW)
サーバーが提供するプレビューストリーム(低解像度)で受け取ります。プレビューストリームがないキャンペーンは本ストリームで再生します。デフォルトは .live(本解像度)です。
sound.muteOnStart = trueミュートで開始します。音は isMuted = false で出します
overlay.ui = .hiddenチャット・商品などのオーバーレイを隠します。再生中に overlayUI で再表示できます
Android sound.isVolumeKeyEnabled音量キーで音を調整できるかどうか。指定しない場合、PREVIEW はオフ、LIVE はオンになります
プリセット 1 つで iOS ShoplivePlayerConfiguration.preview・Android ShoplivePlayerConfiguration.PREVIEW が上の表の値(type = .preview・ミュート・オーバーレイ非表示)に in-App PIP の設定までまとめて指定します。変えたい値だけ上書きしても構いません — iOS は var config = ShoplivePlayerConfiguration.preview の後に config.overlay.ui = .builtIn のように、Android は ShoplivePlayerConfiguration.PREVIEW.copy(…) で指定します。フィードに埋め込まず浮かぶ小さなウィンドウで始めるときは ShoplivePlayOptions.preview(Android ShoplivePlayOptions.PREVIEW)と組み合わせます(ミッション 5)。

タップ → 本視聴へ

プレビューは低解像度のため、そのまま拡大せず本解像度(.live)のプレーヤーを新しく開きます。プレビューのビューは停止しておきます。

playerView.stop()                      // プレビューは停止して
let player = ShoplivePlayerViewController(campaignKey: "YOUR_CAMPAIGN_KEY")   // 本解像度で新しく開く
present(player, animated: true)
playerView.stop()                      // プレビューは停止して
ShoplivePlayer(context).start(campaignKey = "YOUR_CAMPAIGN_KEY")   // デフォルト type = LIVE
// 本解像度で本視聴へ切り替え — iOS は開いているセッションのまま切り替え
await ShoplivePlayer.instance.start("YOUR_CAMPAIGN_KEY",
    configuration: const ShoplivePlayerConfiguration(type: ShoplivePlayerType.live));

ハイブリッド連携(Web + Mobile SDK)

ハイブリッドアプリを連携する場合は、配信リストは Webview にそのまま残し、配信をタップしたときだけネイティブプレーヤーに渡して再生する方式をおすすめします。

方式自前で管理するもの
リストもネイティブで実装リスト UI をネイティブで実装し、項目ごとにプレビューを表示します(上の View 方式)。リストの構成・ページング・状態更新をすべて自前で管理します。
Webview のリストを再利用(この節)リストは Webview 内でプラグインの setOverall がそのまま描画します。タップされた配信のキーだけをネイティブに渡し、ネイティブは再生のみ行います。
ブリッジで渡す値は campaignKey ひとつ Webview とネイティブは別レイヤーです。accessKey はネイティブが既に保持しているため(Shoplive.initialize)、Webview からネイティブへ渡す値は campaignKey ひとつだけです。
  1. Webview — リストを描画し、タップ時にネイティブへ campaignKey を渡す
  2. ネイティブ — 渡された値を受け取る
  3. ネイティブ — SDK で再生する

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

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

<script src="https://static.shoplive.cloud/shoplive.js"></script>
<script>
  var APP_INTERFACE = "ShopLiveAppInterface";
  var messageCallback = {
    ON_CLICK_CAMPAIGN_LIST_ITEM: function (payload) {
      var campaignKey = payload.campaign.campaignKey;   // payload = { ak, campaign }
      // Android · iOS — campaignKey だけをネイティブへ
      if (window[APP_INTERFACE]) {
        window[APP_INTERFACE].postMessage(campaignKey);                        // Android
      } else if (window.webkit?.messageHandlers?.[APP_INTERFACE]) {
        window.webkit.messageHandlers[APP_INTERFACE].postMessage(campaignKey); // iOS
      } else {
        // ネイティブインターフェースなし(通常の Web)— Web モーダル再生へ
        cloud.shoplive.showFeaturedPlayerModal({ campaignKey: campaignKey });
      }
    }
  };
  cloud.shoplive.initPlugin({ accessKey: "YOUR_ACCESS_KEY", messageCallback: messageCallback });
</script>
<div id="shoplive-overall"></div>
<script defer>
  cloud.shoplive.setOverall("shoplive-overall");
</script>
iOS の Webview 設定 インライン再生には WKWebViewConfiguration の allowsInlineMediaPlayback = true が必要です(上記コードとは別の、Webview 自体の設定)。

ステップ 2 · ネイティブでの受け取り

上記コードが呼び出すインターフェース名(ShopLiveAppInterface)をネイティブ側にそのまま登録します。この登録は Shoplive SDK の機能ではなく、Webview が標準で提供する JS ↔ ネイティブ通信です。やり取りするのは campaignKey 文字列ひとつだけなので、解析なしでそのまま受け取れます。

let configuration = WKWebViewConfiguration()
configuration.allowsInlineMediaPlayback = true
configuration.userContentController.add(self, name: "ShopLiveAppInterface")   // WKWebView の生成前(この configuration で作る前)に登録
let webView = WKWebView(frame: .zero, configuration: configuration)

// WKScriptMessageHandler
func userContentController(_ controller: WKUserContentController,
                           didReceive message: WKScriptMessage) {
    guard message.name == "ShopLiveAppInterface",
          let campaignKey = message.body as? String else { return }
    playLive(campaignKey)   // ステップ 3 · ネイティブで再生
}
webView.addJavascriptInterface(ShopLiveAppInterface(activity), "ShopLiveAppInterface")

class ShopLiveAppInterface(private val activity: Activity) {
    @JavascriptInterface                     // バックグラウンドスレッドで呼ばれる
    fun postMessage(campaignKey: String) {
        activity.runOnUiThread { playLive(activity, campaignKey) }   // ステップ 3 · ネイティブで再生
    }
}
// webview_flutter — ページに window.ShopLiveAppInterface.postMessage を公開
controller.addJavaScriptChannel(
  "ShopLiveAppInterface",
  onMessageReceived: (JavaScriptMessage message) {
    playLive(message.message);   // ステップ 3 · ネイティブで再生
  },
);
ブリッジは Webview 内のどのページからも呼び出せます Webview が自社ページ以外の URL(商品リンクなど)へ遷移し得る場合、そのページからも ShopLiveAppInterface を呼べます。外部への遷移は新規ウィンドウまたは外部ブラウザで開き、Webview 内には自社ページだけが残るようにしてください。

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

ブリッジで受け取った campaignKey ひとつでフルスクリーンのプレーヤーを起動できます — ミッション 1 と同じです。ゲスト再生の場合で、アプリ起動時に Shoplive.initialize が呼ばれていることが前提です(クイックスタート)。

func playLive(_ campaignKey: String) {
    let player = ShoplivePlayerViewController(campaignKey: campaignKey)
    present(player, animated: true)
}
fun playLive(context: Context, campaignKey: String) {
    ShoplivePlayer(context).start(campaignKey = campaignKey)
}
Future<void> playLive(String campaignKey) =>
    ShoplivePlayer.instance.start(campaignKey);

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

ゲスト再生なら省略します。ログインした視聴者なら再生の直前に setUser を呼びます(ミッション 3)。JWT は Webview プラグインで使った JWT と同じユーザー基準で発行します。値をどこから取得するかはアプリのログイン構成によって分かれます。

ログインがある場所やること
ネイティブ(アプリがログインを管理)ネイティブが既に値を保持しています。ブリッジの payload はそのまま(campaignKey のみ)にし、ステップ 3 の直前に setUser を呼びます。
Webview のセッションのみネイティブはユーザーを知りません。ステップ 1 の payload に認証値をオブジェクトで載せて送り、ステップ 2 は文字列ではなく JSON を解析するように変更します。
// ステップ 1 の拡張 — オブジェクトで送る(JWT の例)
var bridgePayload = { campaignKey: campaignKey, authToken: currentUserJWT };
if (window[APP_INTERFACE]) {
  window[APP_INTERFACE].postMessage(JSON.stringify(bridgePayload));       // Android — String only
} else if (window.webkit?.messageHandlers?.[APP_INTERFACE]) {
  window.webkit.messageHandlers[APP_INTERFACE].postMessage(bridgePayload); // iOS — object as is
}
// ログインユーザー — 再生の直前に(ミッション 3)
Shoplive.setUser(.token(jwt: jwt))
// 続けてステップ 3 のとおり再生
// ログインユーザー — 再生の直前に(ミッション 3)
Shoplive.setUser(ShopliveUser.Token(jwt))
// 続けてステップ 3 のとおり再生
// ログインユーザー — 再生の直前に(ミッション 3)
await Shoplive.setUser(ShopliveUser.token(jwt));
// 続けてステップ 3 のとおり再生
プラットフォームの違い Android の View 方式ではOS PIP(アプリ全体の縮小)を提供しません(埋め込みビューはホスト Activity の所有物です)。OS PIP が必要な場合は Activity 方式(ミッション 1)を使ってください。in-App のフローティング PIP(ミッション 5)は両プラットフォーム・両形態でサポートされます。

完了確認

  • フィードにプレビューを複数並べても途切れずに再生される(プレビューストリーム)
  • フィードのスクロール中も再生が続く(Android: bindLifecycle を接続)
  • タップすると本視聴が本解像度で開く
  • (ハイブリッド連携) Web のプレビュータップがアプリのネイティブプレーヤーにつながる

前へ ← ミッション 3次へ → ミッション 5 · PIP の適用

ミッション · Player

ミッション 5 · PIP の適用

目標別の画面へ移動したりホームに出たりしても、小さなウィンドウで視聴を続ける
事前準備ミッション 1 または ミッション 4 完了

PIP はフルスクリーン/View、iOS/Android のいずれでも同じメソッドです。「どのビューを PIP に送るか」はSDK が把握しています — ホスト側がビューを渡す必要はありません。

種類説明制御
in-App PIPアプリ内に浮かぶ小さなフローティングウィンドウ。画面遷移をまたいで維持されるenterPictureInPicture() / exitPictureInPicture()
OS PIPホームに出てもシステム PIP で再生を継続configuration.pip.isOSPipEnabled (自動、フィールド辞典)
player.enterPictureInPicture()      // 小さなウィンドウへ
player.exitPictureInPicture()       // 元に戻す
if player.isInPictureInPicture { /* … */ }

// 位置・倍率などは設定で
var cfg = ShoplivePlayerConfiguration()
cfg.pip.defaultPosition = .bottomRight
cfg.pip.scale = 0.4
cfg.pip.isOSPipEnabled = true
player.enterPictureInPicture()      // 小さなウィンドウへ
player.exitPictureInPicture()       // 元に戻す
if (player.isInPictureInPicture) { /* … */ }

// 位置・倍率などは設定で
val cfg = ShoplivePlayerConfiguration(
    pip = ShoplivePlayerConfiguration.PipOptions(
        defaultPosition = ShoplivePipPosition.BOTTOM_RIGHT,
        scale = 0.4f,
        isOSPipEnabled = true))     // OS PIP は Activity 方式専用
final player = ShoplivePlayer.instance;
await player.enterPictureInPicture();   // 小さなウィンドウへ
await player.exitPictureInPicture();    // 元の画面へ
if (await player.isInPictureInPicture()) { /* … */ }

// 位置・サイズなどは設定で(現在の設定に上書きマージ)
await player.setConfiguration(const ShoplivePlayerConfiguration(
    pip: ShoplivePipOptions(
        defaultPosition: ShoplivePipPosition.bottomRight,
        scale: 0.4,
        isOSPipEnabled: true)));   // Android: ホスト Activity に android:supportsPictureInPicture="true" も追加
自動 PIP — 呼び出しなしで切り替え 商品ランディングへ画面遷移するとき: navigation.actionOnNavigation = PIP (ミッション 6) · アプリがバックグラウンドに入るとき: pip.isOSPipEnabled — 設定だけで自動的に切り替わります。

PIP で直接開始する

上は再生中に小さな窓へ移す方法です。フルスクリーンをまったく経由せず、 最初から小さな窓で開始することもできます — 商品詳細でプレビューのように出す場合です。

var options = ShoplivePlayOptions()
options.presentationStyle = .pip            // 既定は .fullScreen

player.play(campaignKey: key, options: options)
ShoplivePlayer(this).start(
    campaignKey = key,
    options = ShoplivePlayOptions(
        presentationStyle = ShoplivePlayerPresentationStyle.PIP))   // 既定 FULLSCREEN
await ShoplivePlayer.instance.start(key,
    playOptions: const ShoplivePlayOptions(
        presentationStyle: ShoplivePresentationStyle.pip));   // 既定は fullscreen — pip.isInAppPipEnabled がオフなら INVALID_ARGUMENT
フィールド値意味
presentationStyleFULLSCREEN(既定)/ PIP この再生をどの表示で開始するか。既定値が現行動作なので既存コードに影響しません
両プラットフォームとも in-App PIP で開始します PIP で開始すると、SDK が持つフローティング窓(in-App PIP)で再生します。pip.isInAppPipEnabled がオフの場合、 iOS は何も通知せずフルスクリーンで再生します。Android で ShoplivePlayer.intent() から作ったインテントは常にフルスクリーンで、 PIP は無視されます — PIP で開始するには start() を使ってください。

プレビュープリセット

「小さな窓 + ミュート + オーバーレイ非表示」の組み合わせは、iOS に定数として用意されています。 設定と開始オプションは別なので、両方を渡します。Android にはプリセットがないため、同じ値を直接指定します。

player.configuration = .preview                     // ミュート・オーバーレイ非表示・in-App PIP
player.play(campaignKey: key, options: .preview)    // presentationStyle = .pip

// プリセットを出発点にして調整(値型なのでコピーされます)
var cfg = ShoplivePlayerConfiguration.preview
cfg.pip.defaultPosition = .bottomLeft
ShoplivePlayer(this).start(
    campaignKey = key,
    options = ShoplivePlayOptions(presentationStyle = ShoplivePlayerPresentationStyle.PIP),
    configuration = ShoplivePlayerConfiguration(
        sound = ShoplivePlayerConfiguration.SoundOptions(muteOnStart = true),             // ミュート
        overlay = ShoplivePlayerConfiguration.OverlayOptions(ui = ShopliveOverlayUIMode.HIDDEN)))  // オーバーレイ非表示
// Flutter にはプリセットがありません — 同じ値を直接設定
await ShoplivePlayer.instance.start(key,
    playOptions: const ShoplivePlayOptions(presentationStyle: ShoplivePresentationStyle.pip),
    configuration: const ShoplivePlayerConfiguration(
        sound: ShopliveSoundOptions(muteOnStart: true),                       // ミュート
        overlay: ShopliveOverlayOptions(ui: ShopliveOverlayUIMode.hidden)));   // オーバーレイ非表示

完了確認

  • 縮小ボタン/API 呼び出しで小さなウィンドウに切り替わり、ドラッグで移動できる
  • 小さなウィンドウをタップ → フルスクリーンに復帰し、再生が途切れない
  • ホームへ移動すると OS PIP に引き継がれる (設定時)

前へ ← ミッション 4次へ → ミッション 6 · イベント · 商品 · クーポン

ミッション · Player

ミッション 6 · イベント · 商品 · クーポンの連携

目標商品タップ → 自社の詳細ページへ遷移、クーポン発行の処理、ステータスイベントの受信
事前準備ミッション 1 完了 · 商品 URL のルーティングルール (導入企業の設定)

デリゲートの構造 — メソッドはたった 2 つ

チャネル性格例
event (通知)必要な case だけ選んで処理ステータス変化 · キャンペーン情報/ステータス · 再生 · 接続状態 · アナリティクス · エラー · Web コマンド
request (応答が必要)respond を必ず呼び出す商品ランディング(navigation) · クーポン · カスタムアクション
extension MyViewController: ShoplivePlayerDelegate {

  // 通知 — 全ケース。必要な case だけ残して削除しても構いません
  func player(_ p: ShoplivePlayerControlling, didReceive event: ShoplivePlayerEvent) {
    switch event {
    case .stateChanged(let s):              // 再生ライフサイクル (.inAppPIP への移行/復帰を含む)
        print("state:", s)
    case .campaignStatusChanged(let c):     // .ready / .live / .ended
        liveBadge.isHidden = (c != .live)
    case .campaignInfoReceived(let info):   // キャンペーンのメタデータ受信 (タイトル・ブランドなど)
        titleLabel.text = info.title
    case .playback(let pb):                 // pb.event: .started / .rendering / .rebufferingStarted / .rebufferingEnded / .ended …
        if pb.event == .rebufferingStarted { spinner.isHidden = false }
        if pb.event == .rebufferingEnded { spinner.isHidden = true }
    case .connectionStateChanged(let cs):   // 共通の接続状態
        networkBanner.isHidden = (cs != .reconnecting)
    case .userNameUpdateRequested(let payload):  // ニックネーム変更のリクエスト → アプリのプロフィール画面
        openProfileEditor(payload)
    case .analytics(let info):              // 視聴統計 → 自社アナリティクスへ連携
        tracker.log(info.campaignKey, info.isPlaying, info.durationMs)
    case .error(let e):                     // 復旧不能なエラーのみ通知される
        show(e.message)
    case .webCommand(let name, let payload):  // オーバーレイ(Web)から届くカスタムコマンド — 例: ログイン要求
        handleWebCommand(name, payload)
    }
  }

  func player(_ p: ShoplivePlayerControlling, didRequest request: ShoplivePlayerRequest) {
    switch request {
    case .navigation(let url):  router.open(url)      // 商品/バナーのランディング
    case .coupon(let id, let respond):            // 発行処理の後に結果を返信 — status: ポップアップ処理(.show/.hide/.keep)、alertType: .alert/.toast
        respond(ShopliveCouponResult(couponId: id, success: true, status: .hide, alertType: .toast))
    case .customWebAction(let id, _, _, let respond):  // 動作を実行した後に結果を返信
        respond(ShopliveCustomActionResult(id: id, success: true, status: .keep, alertType: .toast))
    }
  }
}
val myDelegate = object : ShoplivePlayerDelegate {

  // 通知 — 全ケース。必要な case だけ残して削除しても構いません (既定実装は empty)
  override fun onEvent(p: ShoplivePlayerControlling, event: ShoplivePlayerEvent) {
    when (event) {
      is ShoplivePlayerEvent.StateChanged ->             // 再生ライフサイクル (IN_APP_PIP への移行/復帰を含む)
          log(event.state)
      is ShoplivePlayerEvent.CampaignStatusChanged ->    // READY / LIVE / ENDED
          liveBadge.isVisible = event.status == ShopliveCampaignStatus.LIVE
      is ShoplivePlayerEvent.CampaignInfoReceived ->     // キャンペーンのメタデータ受信 (タイトル・ブランドなど)
          titleView.text = event.info.title
      is ShoplivePlayerEvent.Playback -> when (event.event) {  // event.event: Started / Rendering / RebufferingStarted / RebufferingEnded / Ended …
          is ShoplivePlaybackEvent.RebufferingStarted -> spinner.isVisible = true
          is ShoplivePlaybackEvent.RebufferingEnded -> spinner.isVisible = false
          else -> Unit
      }
      is ShoplivePlayerEvent.ConnectionStateChanged ->   // 共通の接続状態
          networkBanner.isVisible = event.state == ShopliveConnectionState.RECONNECTING
      is ShoplivePlayerEvent.UserNameUpdateRequested ->  // ニックネーム変更のリクエスト → アプリのプロフィール画面
          openProfileEditor(event.payload)
      is ShoplivePlayerEvent.Analytics ->                // 視聴統計 → 自社アナリティクスへ連携
          tracker.log(event.info.campaignKey, event.info.isPlaying, event.info.durationMs)
      is ShoplivePlayerEvent.Error ->                    // 復旧不能なエラーのみ通知される
          show(event.error.message)
      is ShoplivePlayerEvent.WebCommandReceived ->      // オーバーレイ(Web)から届くカスタムコマンド — 例: ログイン要求
          handleWebCommand(event.name, event.payload)
    }
  }

  override fun onRequest(p: ShoplivePlayerControlling, request: ShoplivePlayerRequest) {
    when (request) {
      is ShoplivePlayerRequest.Navigation -> router.open(request.url)
      is ShoplivePlayerRequest.Coupon ->             // 発行処理の後に結果を返信 — status: ポップアップ処理(SHOW/HIDE/KEEP)、alertType: ALERT/TOAST
          request.respond(ShopliveCouponResult(couponId = request.id, success = true,
              status = ShopliveResultStatus.HIDE, alertType = ShopliveResultAlertType.TOAST))
      is ShoplivePlayerRequest.CustomWebAction ->    // 動作を実行した後に結果を返信
          request.respond(ShopliveCustomActionResult(id = request.id, success = true,
              status = ShopliveResultStatus.KEEP, alertType = ShopliveResultAlertType.TOAST))
    }
  }
}
final player = ShoplivePlayer.instance;

// 通知 — events ストリーム。必要な case だけ残して構いません
player.events.listen((event) {
  switch (event) {
    case ShoplivePlayerStateChanged(:final state):          // 再生ライフサイクル(inAppPip の開始・終了を含む)
      log(state);
    case ShopliveCampaignStatusChanged(:final status):      // ready / live / ended
      showLiveBadge(status == ShopliveCampaignStatus.live);
    case ShopliveCampaignInfoReceived(:final info):         // 配信メタ情報を受信(タイトル・ブランドなど)
      setTitle(info.title);
    case ShoplivePlaybackEvent(:final kind):                // 再生の種類: started / rendering / rebufferingStarted / rebufferingEnded / ended …
      if (kind == ShoplivePlaybackEventKind.rebufferingStarted) showSpinner(true);
      if (kind == ShoplivePlaybackEventKind.rebufferingEnded) showSpinner(false);
    case ShopliveConnectionStateChanged(:final state):      // 共通の接続状態
      showNetworkBanner(state == ShopliveConnectionState.reconnecting);
    case ShopliveUserNameUpdateRequested(:final payload):   // ニックネーム変更の要求 → アプリのプロフィール画面
      openProfileEditor(payload);
    case ShopliveAnalyticsEvent e:                          // 視聴統計 → 自社の分析へ転送
      tracker.log(e.campaignKey, e.isPlaying, e.durationMs);
    case ShoplivePlayerErrorEvent(:final error):            // 回復不能なエラーのみ届く
      show(error.message);
    case ShopliveWebCommandReceived(:final name, :final payload):   // オーバーレイ(Web)からのカスタムコマンド — 例: ログイン要求
      handleWebCommand(name, payload);
    default:
      break;
  }
});

// リクエスト — 種類ごとに 1 ストリーム。セッション中は購読を維持します
player.navigationRequests.listen((request) => router.open(request.url));
player.couponRequests.listen((request) {                    // 発行後に結果を報告(respond は 1 回のみ有効 · 既定 10 秒でタイムアウト)
  request.respond(const ShopliveCouponResult(success: true,
      status: ShopliveResultStatus.hide, alertType: ShopliveResultAlertType.toast));
});
player.customWebActionRequests.listen((request) {           // アクション実行後に結果を報告
  request.respond(const ShopliveCustomActionResult(success: true,
      status: ShopliveResultStatus.keep, alertType: ShopliveResultAlertType.toast));
});

ShoplivePlayerEvent の種類 — didReceive event で届く通知

すべて任意の処理です — 必要な case だけ選んで処理し、残りは無視して構いません。 表記は iOS 基準(camelCase)で、Android は同名の sealed class(ShoplivePlayerEvent.StateChanged など)で、webCommand だけ WebCommandReceived と名前が異なります。

ShoplivePlayerEvent の宣言全体を見る (Swift · Kotlin · Dart)
public enum ShoplivePlayerEvent {
    case stateChanged(ShoplivePlayerState)                  // .inAppPIP への移行/復帰もこのイベントで通知
    case campaignStatusChanged(ShopliveCampaignStatus)      // .ready / .live / .ended
    case campaignInfoReceived(ShopliveCampaignInfo)
    case playback(PlaybackEvent)                    // started/rendering/rebuffering/ended…
    case connectionStateChanged(ShopliveConnectionState)    // 共通の接続状態
    case userNameUpdateRequested([String: Any])
    case analytics(info: ShopliveAnalyticsInfo)
    case error(ShopliveError)
    case webCommand(name: String, payload: [String: Any]?)  // オーバーレイ(Web) → アプリのカスタムコマンド
}

// ShopliveCore — 種類は event で区別
public class PlaybackEvent: NSObject {
    public let event: PlaybackEventType
    public let datas: [String: Any]
}
public enum PlaybackEventType: Int {
    case request, started, audioLoaded, rendering, ended,
         rebufferingStarted, rebufferingEnded, error, buffering
}

public struct ShopliveAnalyticsInfo {
    public let campaignKey: String
    public let isPlaying: Bool
    public let isMuted: Bool
    public let durationMs: Int64
    public let campaign: ShopliveCampaign?
    public let brand: ShopliveBrand?
}
sealed class ShoplivePlayerEvent {
    data class StateChanged(val state: ShoplivePlayerState) : ShoplivePlayerEvent()               // IN_APP_PIP への移行/復帰もこのイベントで通知
    data class CampaignStatusChanged(val status: ShopliveCampaignStatus) : ShoplivePlayerEvent()  // READY / LIVE / ENDED
    data class CampaignInfoReceived(val info: ShopliveCampaignInfo) : ShoplivePlayerEvent()
    data class Playback(val event: ShoplivePlaybackEvent) : ShoplivePlayerEvent()         // 種類はサブタイプで区別
    data class ConnectionStateChanged(val state: ShopliveConnectionState) : ShoplivePlayerEvent() // 共通の接続状態
    data class UserNameUpdateRequested(val payload: Map<String, Any?>) : ShoplivePlayerEvent()
    data class Analytics(val info: ShopliveAnalyticsInfo) : ShoplivePlayerEvent()
    data class Error(val error: ShopliveError) : ShoplivePlayerEvent()
    data class WebCommandReceived(val name: String, val payload: Map<String, Any?>) : ShoplivePlayerEvent()  // オーバーレイ(Web) → アプリのカスタムコマンド
}

sealed class ShoplivePlaybackEvent {
    abstract val timestampMs: Long?
    // Requested · Started · Rendering · Buffering · AudioLoaded · RebufferingStarted · RebufferingEnded · Ended
    data class Failed(override val timestampMs: Long?, val code: Int? = null, val message: String? = null) : ShoplivePlaybackEvent()
}

data class ShopliveAnalyticsInfo(
    val campaignKey: String, val isPlaying: Boolean, val isMuted: Boolean, val durationMs: Long,
    val campaign: ShopliveCampaign? = null, val brand: ShopliveBrand? = null,
)
// ShoplivePlayer.instance.events に以下のサブクラスが届きます
sealed class ShoplivePlayerEvent {}
class ShoplivePlayerStateChanged { final ShoplivePlayerState state; }          // inAppPip の開始・終了もこのイベント
class ShopliveCampaignStatusChanged { final ShopliveCampaignStatus status; }    // ready / live / ended
class ShopliveCampaignInfoReceived { final ShopliveCampaignInfo info; }
class ShoplivePlaybackEvent { final ShoplivePlaybackEventKind kind; final int? timestampMs, code; final String? message; }
// kind: requested · started · audioLoaded · rendering · buffering · rebufferingStarted · rebufferingEnded · ended · failed(code・message は failed のときのみ)
class ShopliveConnectionStateChanged { final ShopliveConnectionState state; }
class ShopliveUserNameUpdateRequested { final Map<String, Object?> payload; }
class ShopliveAnalyticsEvent { final String campaignKey; final bool isPlaying, isMuted; final int durationMs;
                               final ShopliveCampaign? campaign; final ShopliveBrand? brand; }
class ShoplivePlayerErrorEvent { final ShopliveError error; }
class ShopliveWebCommandReceived { final String name; final Map<String, Object?> payload; }   // オーバーレイ(Web)→ アプリへのカスタムコマンド
class ShoplivePlayerUnknownEvent { final String rawType; }                     // 未知・不正な値はここへ — ストリームは途切れません
casepayloadいつ届くか主な用途
stateChangedShoplivePlayerStateプレーヤーのライフサイクル変化 (idle → loading → playing…)。in-App PIP への移行/復帰(inAppPIP)もこのイベントで通知再生状態に応じたアプリ UI の更新 — ミッション 5
campaignStatusChangedShopliveCampaignStatus配信ステータスの変化: ready(待機) → live(配信中) → ended(終了)ライブバッジなど LIVE限定 の独自 UI のトグル — 画面用語
campaignInfoReceivedShopliveCampaignInfoキャンペーンのメタデータ(タイトル・ブランドなど)の取得完了時に 1 回独自 UI へのキャンペーン情報の表示
playbackPlaybackEvent (Android ShoplivePlaybackEvent)再生パイプラインのイベント: started / rendering / rebufferingStarted / rebufferingEnded / ended など。iOS は pb.event、Android はサブタイプで区別ローディングスピナー・バッファリング案内の表示
connectionStateChangedShopliveConnectionState接続状態の変化 (connecting / connected / reconnecting / disconnected)ネットワーク接続状態の案内
userNameUpdateRequested[String: Any]視聴者がオーバーレイでニックネームの変更をリクエストしたときアプリのプロフィール変更画面への接続 — ミッション 3
analyticsShopliveAnalyticsInfo視聴統計イベントの発生時 (campaignKey · isPlaying · isMuted · durationMs · campaign/brand)自社アナリティクス(GA/Amplitude など)への連携
errorShopliveError復旧不能なエラーでセッションが終了するときのみ。自動復旧中の問題は届きませんエラー案内 + リトライ UI — エラー処理
webCommandname + payloadオーバーレイ(Web)がアプリへカスタムコマンドを送ったとき (例: ログイン要求)。Android 3.0.3 から Web プレーヤーのユーザー行動ログも name = "EVENT_LOG" で届きますアプリ画面の遷移・ログイン誘導など。逆方向は send(command:payload:)

request の 3 種類 — アプリがやるべきこと

リクエストいつアプリがやること
navigation(url:)商品/バナーのタップ (画面用語 ④)該当 URL への画面遷移(ディープリンク/WebView/ネイティブ詳細)。唯一の必須処理
coupon(id:respond:)クーポンボタン (⑤ LIVE限定)発行処理の後 respond(ShopliveCouponResult(…)) で結果を返信 → 成功/失敗の UI がオーバーレイに反映。status(show / hide / keep)でクーポンポップアップの処理を、alertType(alert / toast)で案内の形を決めます。3.0.3 時点で省略した status は hide、alertType は alert です(両プラットフォーム共通・Flutter 3.0.3 プラグインは alertType を省略すると toast)。Android で Web のデフォルトに従うには alertType = null
customWebAction(…)導入企業が定義したカスタムボタン動作を実行した後 respond(ShopliveCustomActionResult(…))

アプリ → オーバーレイへコマンドを送る

player.send(command: "HIGHLIGHT_PRODUCT", payload: ["sku": "A-1024"])
player.send(command = "HIGHLIGHT_PRODUCT", payload = mapOf("sku" to "A-1024"))
await ShoplivePlayer.instance.send("HIGHLIGHT_PRODUCT", {"sku": "A-1024"});
ネットワーク状態の確認 ネットワークの状況は playback(rebufferingStarted…)・connectionStateChanged(型辞典)イベントで確認します。

完了確認

  • 商品タップ → 自社の詳細ページが開く (プレーヤーは actionOnNavigation のポリシーに従って PIP/維持/終了 — ミッション 5)
  • クーポンタップ → 発行処理の後、オーバーレイに成功表示が出る
  • 配信終了時に campaignStatusChanged(ended) が受信される

前へ ← ミッション 5次へ → ミッション 7 · UI カスタマイズ

ミッション · Player

ミッション 7 · UI カスタマイズ & 配置の変更

目標ブランドの色・フォントを適用する、または標準 UI をオフにして独自 UI を好きな位置に配置する
事前準備ミッション 1・ミッション 6 完了 (独自 UI にはイベントデータが必要)

カスタマイズは 2 種類 — SDK のアップデートなしで可能

種類誰が変更するか変更できる範囲反映方法
ネイティブのカスタマイズ加盟店 — アプリのコードで直接標準の外観オプション(下の方法 ①)· 既定のオーバーレイを隠して自作した UI(方法 ②)次のアプリ配布で反映 — 既に使っている SDK の API で行うため SDK のアップデートは不要
Web によるカスタマイズShopliveプレーヤーのオーバーレイ(チャット・商品・お知らせなど)は Shoplive が提供する Web レイヤーのため、デザイン・配置を Shoplive 側で変更しますShoplive 側で反映 — SDK のアップデートもアプリ配布も不要。ご要望は Shoplive チームにご相談ください

方法 ① 標準の外観オプション — appearance

ローディングの色・ステータスバー・チャットのフォントなどは設定だけで変更できます。要素の表示/スタイルレベルの変更であれば、この方法で十分です。

オプションiOSAndroid
ローディングインジケーターの色 (画面用語 ⑧)indicatorColor: UIColorindicatorColor: @ColorInt Int
ローディングアニメーションloadingAnimationImages: [UIImage]?loadingAnimation: @DrawableRes Int
ステータスバーの表示—isStatusBarVisible
チャット入力フォント (③)chatInputFont: UIFont?chatInputTypeface: Typeface
画面キャプチャの許可allowScreenCaptureallowScreenCapture

方法 ② 要素の再配置 — 標準 UI を隠して独自 UI を描く

ボタンや要素の位置を自由に変えたい場合はこの方法です。標準のオーバーレイ UI を隠し(overlay.ui = hidden)、 アプリが ミッション 6 のイベント/リクエストのデータで独自 UI を描画します — 位置・形・アニメーションはすべてアプリの自由です。

var cfg = ShoplivePlayerConfiguration()
cfg.overlay.ui = .hidden        // 標準のオーバーレイ UI を隠す (コマンドチャネルは維持)
let player = ShoplivePlayerViewController(campaignKey: key, configuration: cfg)
// これ以降、チャット/商品/クーポンの UI はアプリが delegate のデータで自ら描画します
val cfg = ShoplivePlayerConfiguration(
    overlay = ShoplivePlayerConfiguration.OverlayOptions(ui = ShopliveOverlayUIMode.HIDDEN)   // 表示のみを隠す
)
ShoplivePlayer(context).start(campaignKey = key, configuration = cfg)
// これ以降、チャット/商品/クーポンの UI はアプリが delegate のデータで自ら描画します
await ShoplivePlayer.instance.start(key,
    configuration: const ShoplivePlayerConfiguration(
        overlay: ShopliveOverlayOptions(ui: ShopliveOverlayUIMode.hidden)));   // 表示のみ非表示
// これで events・リクエストストリームのデータからチャット・商品・クーポン UI をアプリが描画します
標準オーバーレイ vs 独自 UI
できること / できないこと 標準オーバーレイの内部要素をピクセル単位で移動させる公開 API はありません。方法 ①(標準オプション)または方法 ②(独自 UI)のいずれかを選んでください。 非表示の状態でもコマンドチャネルは生きているため、クーポン・商品・カスタムアクションのデータは引き続き届きます (ミッション 6)。

完了確認

  • ブランドの色/フォントがローディング・チャットに反映される (方法 ①)
  • hidden の状態でも商品タップ・クーポンのイベントがデリゲートに届き続ける (方法 ②)
  • 配信ステータス(ready/live/ended)に応じて独自 UI が正しくトグルされる (画面用語)

前へ ← ミッション 6次へ → ミッション 8 · 番組の配信

ミッション · Streamer

ミッション 8 · 番組の配信

目標アプリからスタジオを開いて配信を開始・終了する
事前準備配信用トークン(Secret Keyで署名) · カメラ/マイクの権限(インストール)

カメラプレビュー・配信開始/終了・チャット・設定・カメラ切替/マイク/ズームまで、スタジオ UI のすべてを SDK が提供します(画面用語)。デリゲートの実装さえ任意です。

ステップ 1 · 配信トークンの設定

視聴用の setUser(ミッション 3)とは別の経路です。配信者の JWT も Shoplive の担当者から受け取った Secret Keyでバックエンドが署名します。

ステップ 2 · スタジオの起動 & ステータス受信

// 1) 配信トークン (必須)
Shoplive.setStreamToken(streamerJwt)

// 2) スタジオの起動
let studio = ShopliveStreamerViewController(campaignKey: "YOUR_CAMPAIGN_KEY")
studio.streamerDelegate = self          // 任意
present(studio, animated: true)

// 3) ステータス受信 (任意)
extension MyViewController: ShopliveStreamerDelegate {
  func streamer(_ s: ShopliveStreamerViewController, didReceive event: ShopliveStreamerEvent) {
    switch event {
    case .stateChanged(let st): print("broadcast:", st)
    case .error(let e):         show(e.message)
    }
  }
}
// 1) 配信トークン (必須)
Shoplive.setStreamToken(streamerJwt)

// 2) スタジオの起動 — ランタイム権限も SDK が要求
ShopliveStreamer(context).apply {
    streamerDelegate = myStreamerDelegate   // 任意
}.start(campaignKey = "YOUR_CAMPAIGN_KEY")

// 3) ステータス受信 (任意)
val myStreamerDelegate = object : ShopliveStreamerDelegate {
  override fun onEvent(s: ShopliveStreamer, event: ShopliveStreamerEvent) {
    when (event) {
      is ShopliveStreamerEvent.StateChanged -> log(event.state)
      is ShopliveStreamerEvent.Error -> show(event.error.message)
    }
  }
}
// 1) 配信トークン(必須)
await Shoplive.setStreamToken(streamerJwt);

final streamer = ShopliveStreamer.instance;
// 3) 状態を受け取る(任意)— start の前に購読
streamer.events.listen((event) {
  switch (event) {
    case ShopliveStreamerStateChanged(:final state):
      log(state);
    case ShopliveStreamerErrorEvent(:final error):
      show(error.message);
    default:
      break;
  }
});

// 2) スタジオを起動 — ランタイム権限の要求も SDK が処理
await streamer.start("YOUR_CAMPAIGN_KEY");

配信画面の隣に Web ページを置く (任意・3.0.3+) — 導入企業の Web ページ URL を渡すと配信画面の右側に付き、左へスワイプして開きます。渡さなければ今までと同じです。iOS ShopliveStreamerViewController(campaignKey:webPageURL:)・Android ShopliveStreamer(context).start(campaignKey, webPageUrl = "https://…")・Flutter streamer.start(campaignKey, webPageUrl: "https://…")。URL は http:// か https:// で始まり、ホストを含む必要があります(Flutter は不正な URL のとき配信画面を開く前に INVALID_ARGUMENT で失敗します)。このページには SDK のネイティブブリッジはありません。

ShopliveStreamerEvent の種類 — didReceive event で届く通知

Player と同じメンタルモデルですが、スタジオ UI を SDK がすべて提供するためイベントは 2 種類とシンプルです。すべて任意の処理です。

ShopliveStreamerEvent の宣言全体を見る (Swift · Kotlin · Dart)
public enum ShopliveStreamerEvent {
    case stateChanged(ShopliveBroadcastState)   // .idle/.preview/.connecting/.live/.reconnecting/.ended
    case error(ShopliveError)           // 復旧不能なエラーのみ
}
sealed class ShopliveStreamerEvent {
    data class StateChanged(val state: ShopliveBroadcastState, val isRehearsal: Boolean) : ShopliveStreamerEvent()  // IDLE/PREVIEW/CONNECTING/LIVE/RECONNECTING/ENDED
    data class Error(val error: ShopliveError) : ShopliveStreamerEvent()          // 復旧不能なエラーのみ
}
sealed class ShopliveStreamerEvent {}
class ShopliveStreamerStateChanged { final ShopliveBroadcastState state; final bool? isRehearsal; }
// state: idle / preview / connecting / live / reconnecting / ended · isRehearsal: iOS では null(リハーサル情報なし)
class ShopliveStreamerErrorEvent { final ShopliveError error; }   // 回復不能なエラーのみ
class ShopliveStreamerUnknownEvent { final String rawType; }
casepayloadいつ届くか主な用途
stateChangedShopliveBroadcastState配信ライフサイクルの変化: idle → preview(カメラプレビュー) → connecting → live(配信中) → ended。ネットワークの瞬断時に reconnecting → live と自動復帰する様子もこのイベントで観測できますアプリの画面遷移・配信中表示などのステータス連携
errorShopliveError復旧不能なエラーで配信が終了するときのみ (例: トークン無効 — ステップ 1)。再接続などの自動復旧中には届きませんエラー案内 + リトライ UI — エラー処理

ShopliveBroadcastState(型辞典): idle → preview → connecting → live → (reconnecting) → ended。 配信終了のポリシー(残り時間・終了中の入場許可など)は CampaignEndOption で維持されます。

今後の互換性 配信方式が今後拡張されても公開 API は変更されないため、今書いた連携コードはそのまま維持されます。

完了確認

  • スタジオに入るとカメラプレビューが表示される (preview ステータス)
  • 配信開始 → 視聴アプリ(ミッション 1)で該当キャンペーンが LIVE として再生される
  • ネットワークの瞬断時に reconnecting → live で自動復帰する

前へ ← ミッション 7次へ → データフィールド辞典

リファレンス

データフィールド辞典

公開 API の設定オブジェクトをフィールド単位で整理しました。すべてのフィールドに既定値があるため、必要なものだけ指定すれば十分です。

ShopliveConfiguration — 初期化 (アプリで 1 回)

利用箇所: クイックスタート · ミッション 2 (コールドスタート)

フィールド型既定値説明
accessKeyString必須顧客企業を識別するキー — Shoplive が発行(各キーの役割)
allowedWebViewDomains[String]空配列 (全許可)オーバーレイ WebView の許可ドメイン。必須ドメインは SDK が自動的に含みます (導入企業の設定)
attributionShopliveAttribution?なし流入トラッキングのまとまり (下記)
appVersion (Android)String?なし3.0.3 で削除 — SDK がアプリのバージョンを自分で読み取るため、渡しません(Flutter 3.0.3 プラグインには残っていますが、値は無視され警告ログだけが出ます)

ShopliveAttribution — 流入トラッキング

フィールド型説明
utmSource / utmCampaign / utmMedium / utmContentString?UTM 4 種
advertisingId (iOS)String?広告識別子 (IDFA) — 収集・受け渡しはアプリの責任。3.0.3 時点では iOS のみ

ShopliveUser — ユーザー情報 (3 形態)

利用箇所: ミッション 3 · ユーザー情報の連携 (判断基準・ハッシュ化・Secret Key を含む)

形態フィールド説明
tokenjwt: Stringサーバー署名 JWT (推奨) — 発行方法
profileid(必須) · name / age / gender / rank(任意) · Android custom(任意、追加の key-value)シンプル認証 — SDK が署名なしの JWT を生成します(権限の検証には使えません)。rank はユーザーランク(-100 〜 100)。id は ハッシュ化を推奨
guest—未ログイン — 何もしません。ゲスト識別子はサーバーが発行します
Shoplive.logout()—ログアウト・ユーザー情報のリセット — ユーザー情報(JWT・プロフィール)を消去し、配信用トークンもあわせて消去します。accessKey・ゲスト識別子・UTM は保持されます

ShoplivePlayerConfiguration — 再生ポリシー

利用箇所: ミッション 1 · ミッション 4 (プレビュー) · ミッション 5 (PIP) · ミッション 7 (UI)

グループフィールド既定値説明
—typelive再生形態 — preview はサーバーが提供するプレビューストリーム(低解像度)で受け取ります(ミッション 4)
pip
ミッション 5
isInAppPipEnabledtruein-App フローティング PIP の許可 (VC/Activity・View 共通)
isOSPipEnabledtrueOS PIP — バックグラウンドに入ると自動 (Android は Activity 方式専用)
defaultPositionbottomRightin-App PIP の既定の角の位置 (ShoplivePipPosition)
scale0.4in-App PIP のサイズ倍率
padding0画面端の余白
fixedWidth (iOS)なしin-App PIP ウィンドウの幅を固定(pt) — 指定すると scale の代わりに使用
floatingOffset (iOS)0in-App PIP の位置に加える追加オフセット
windowProvider (iOS)なしin-App PIP を表示する UIWindow を指定(未指定なら自動で探索)
enterOSPipOnBackPressed (Android)false戻る操作で OS PIP に切り替え(Activity 方式のみ)
aspectRatio (Android)RATIO_9X16in-App PIP の縦横比 (ShoplivePipRatio)
isDragOutToCloseEnabledtruein-App PIP ウィンドウを画面外にドラッグすると閉じる。false なら元の位置に戻り、閉じるボタン・close() でのみ閉じます (3.0.3+)
soundmuteOnStartfalse開始時にミュート
mixWithOthersfalse他アプリの音声と同時に再生
autoResumeOnCallEnded (iOS) / autoResumeOnFocusGained (Android)true通話終了/フォーカス回復時に自動再開
isVolumeKeyEnabled (Android)type による音量キーでの調整を許可 — 指定しない場合、PREVIEW はオフ、LIVE はオン
appearance
ミッション 7
indicatorColorwhiteローディングインジケーターの色
resizeMode (iOS)fill映像フィットの初期値 (fill / fit)。再生中はプレーヤーの resizeMode で変更します(Android はプレーヤーのプロパティのみ)
loadingAnimationImages (iOS) / loadingAnimation (Android)なしカスタムのローディングアニメーション
isStatusBarVisible (Android)trueステータスバーの表示
chatInputFont (iOS) / chatInputTypeface (Android)システムチャット入力フォント (画面用語 ③)
chatSendButtonFont (iOS)システムチャット送信ボタンのフォント
allowScreenCapturetrue画面キャプチャ/録画の許可
navigation
ミッション 6
actionOnNavigationpip商品ランディングなど画面遷移時のプレーヤーの動作: pip / keep / close
shareSchemeなし共有ボタンが使用するアプリの URL スキーム (画面用語 ⑥)
closeWhenAppDestroyed (Android)trueアプリ終了時にプレーヤーも閉じる
overlay
ミッション 7
uibuiltIn標準オーバーレイ UI の表示(builtIn) / 非表示(hidden、コマンドチャネルは維持)
—customParameters空マップ導入企業が定義した key-value をキャンペーンへ渡す (導入企業の設定)

ShoplivePlayOptions — 再生 1 回ごとのオプション

利用箇所: ミッション 1 · ミッション 2 (ディープリンクの referrer)

フィールド型既定値説明
referrerString?なし流入経路の識別子 (ディープリンクの ref など) — 統計に反映
keepWindowStateOnPlayExecutedBoolfalse再生開始時に既存のウィンドウ状態(PIP など)を維持
presentationStyleShoplivePlayerPresentationStylefullScreen開始時の表示 — pip ならフルスクリーンを経ずに in-App PIP で開始します(pip.isInAppPipEnabled = true が必要)。プリセット ShoplivePlayOptions.preview(Android PREVIEW)がこの値を使います
リファレンス

型 & ステータス

両プラットフォームで同型です(iOS は小文字の case、Android は大文字の enum)。

型値意味 · 利用箇所
ShoplivePlayerTypelive · preview再生形態 — preview はプレビューストリーム(低解像度)— ミッション 4
ShoplivePlayerPresentationStylefullScreen · pip
(Android FULLSCREEN · PIP)
再生開始時の表示 — ShoplivePlayOptions · ミッション 5
ShoplivePlayerStateidle · loading · playing · paused · inAppPIP(IN_APP_PIP) · closedプレーヤーのライフサイクル — ミッション 1・5
ShopliveBroadcastStateidle · preview · connecting · live · reconnecting · ended配信のライフサイクル — ミッション 8
ShopliveCampaignStatusready · live · endedキャンペーン(配信)のステータス — LIVE限定 要素の基準 (画面用語)
ShopliveConnectionStateconnecting · connected · reconnecting · disconnected接続状態 — ミッション 6
ShopliveResizeModefill · fit画面のフィット — ミッション 1
ShoplivePipPositiontop · middle · bottom × Left · Center · Right(9 か所、例: bottomRight)in-App PIP の既定位置 — ミッション 5
ShopliveOverlayUIModebuiltIn · hidden標準オーバーレイ UI の表示/非表示 (コマンドチャネルは維持) — ミッション 7
ShopliveNavigationActionpip · keep · close画面遷移時のプレーヤーの動作 — ミッション 6
リファレンス

エラー処理

基本ルール: 自動復旧できるものは SDK が処理し、event でのみ知らせます(例: 接続失敗 → 自動フォールバック・再接続)。復旧できないものだけが error として終了通知されます。 導入企業がエラーコードを見て復旧ロジックを自ら実装する必要はありません。受信方法は ミッション 6 を参照してください。

struct ShopliveError: Error {
    let code: ShopliveErrorCode        // 型付けされた enum
    let message: String
    let rawCode: Int           // 元の数値コード (3.0.3+、code が unknown でも保持)
    let underlying: Error?
    let isRecoverable: Bool    // コードごとの固定値 — true は SDK が再試行・再接続で自ら回復する種類
}
// 3.0.3 から視聴・配信で共通 — import cloud.shoplive.core.publicsurface.ShopliveError (· ShopliveErrorCode)
class ShopliveError(
    val code: ShopliveErrorCode,       // 型付けされた enum
    val rawCode: Int,                  // サーバー・エンジンが返した元の数値コード
    override val message: String?,
    override val cause: Throwable? = null,
    val isRecoverable: Boolean = code.isRecoverable,   // コードごとの固定値 — true は SDK が再試行・再接続で自ら回復する種類
) : Exception(message, cause)
// エラーイベントは ShopliveError を持ちます
class ShopliveError {
  final String code;          // SDK 3.0.3 のエラー定数名 — 値が増えることがあるため不透明な文字列として扱います
  final int? rawCode;         // 元の数値コード(3.0.3+ 両プラットフォームで値あり)
  final String? message;
  final bool recoverable;     // true なら SDK が自ら回復(再試行・再接続)
}

// すべての API は Future を返し、失敗時は PlatformException を投げます
// code: INVALID_ARGUMENT · NO_HOST_SCREEN · NOT_RUNNING · SDK_ERROR
try {
  await ShoplivePlayer.instance.start(campaignKey);
} on PlatformException catch (e) {
  if (e.code == ShoplivePlatformErrorCode.noHostScreen) { /* … */ }
}
状況動作アプリがやること
一時的なネットワーク品質の低下SDK が自動復旧し、playback(rebufferingStarted) を通知なし (必要ならローディング UI)
復旧可能 (isRecoverable = true)接続・ネットワーク・配信待ちなどの一時的な状態 — SDK が再試行・再接続で自ら回復待機 — ユーザーへの案内程度
復旧不能 (isRecoverable = false)セッション終了code/message を案内した上でリトライ UI を提供
リファレンス

導入企業が直接設定する項目

導入企業の環境に合わせてコードから直接指定する値です。

項目どこで用途
商品/ランディング URL の処理ShoplivePlayerRequest.navigation (ミッション 6)商品・バナーのタップ時に遷移する画面をアプリが決定 (ディープリンク/WebView/ネイティブ)
共有スキームnavigation.shareScheme (フィールド辞典)共有ボタンが使用するアプリの URL スキーム
WebView の許可ドメインallowedWebViewDomains (フィールド辞典)オーバーレイが開けるドメインのホワイトリスト
流入パラメータattribution · ShoplivePlayOptions.referrer (フィールド辞典)UTM / 広告識別子 / リファラー
カスタムパラメータcustomParameters (フィールド辞典)導入企業が定義した key-value の受け渡し
直接指定できないもの オーバーレイ WebView の base URL は再生制御チャネルであるため、導入企業側では指定しません(内部インフラ)。オンプレミス/専用環境の URL が必要な場合は、Shoplive チームと協議のうえ環境プリセットとして対応します。
リファレンス

トラブルシューティング & FAQ

再生が始まりません

① initialize が再生より先に呼ばれているか(クイックスタート)、② accessKey・campaignKey が有効か、 ③ セキュアなキャンペーンであれば setUser(token)(ミッション 3)が設定されているかを確認してください。復旧不能なエラーは error イベントの code/message(エラー処理)で原因を確認できます。

商品をタップしても何も起こりません

ShoplivePlayerRequest.navigation(url:) はアプリが必ず処理しなければならないリクエストです。デリゲートの request メソッドで URL ルーティングを実装しているか確認してください (ミッション 6)。

標準のチャット UI を隠して独自 UI を使えますか?

はい。overlay.ui = hidden で標準 UI の表示だけをオフにすれば済みます。この状態でもイベント/リクエストのデータは届き続けるため、アプリが独自 UI を描画できます (ミッション 7)。

チャット・クーポンが表示されません

これらの要素は LIVE限定 です。キャンペーンが ready(配信前)または ended(終了)の場合は表示されません (画面用語ガイド)。

Android の埋め込み View で OS PIP が動きません

意図された動作です。OS PIP はホスト Activity 全体を縮小するため、Activity 方式専用です。View 方式では in-App のフローティング PIP(enterPictureInPicture() — ミッション 5)を使用してください。