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 · 番組の配信 |
提供形態 (プラットフォーム対応)
| 形態 | iOS | Android |
|---|---|---|
| Player · フルスクリーン | ShoplivePlayerViewController (present) | ShoplivePlayer(context).start(…) (Activity) |
| Player · 埋め込み | ShoplivePlayerView (UIView) | ShoplivePlayerView (FrameLayout) |
| Streamer · スタジオ | ShopliveStreamerViewController | ShopliveStreamer(context).start(…) |
要件
| 項目 | iOS | Android | Flutter |
|---|---|---|---|
| 最小 OS | iOS 15.0 | Android 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 Manager | Maven リポジトリ (sdk.shoplive.cloud) | pub.dev (shoplive_player · shoplive_streamer) |
| 現在のバージョン | 3.0.3 | 3.0.3 | 3.0.3 |
インストール
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 リポジトリはプラグインが自動で追加します
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 Key | JWT の署名に使う秘密の値。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 に同梱されるデモアプリは、本ガイドのミッションと同じケースメニュー(ミッションマップ)で構成されています。 コードを書く前にデモアプリで動作を確認すると、実装の理解が早まります。
- 標準モード — Shoplive デモ設定: インストール直後、入力なしですべての機能(全ミッション)をデモキャンペーンで確認できます。
- 自社情報モード — 導入企業の設定を入力: 設定画面で発行済みの
accessKey·campaignKey· (任意)ユーザー JWT(発行方法)を入力すると、 自社のキャンペーンと自社のユーザーで同じケースを体験できます。 - 各ケース画面には本ガイドの該当ミッション番号が表示されるため、デモで見た動作 → ガイドのコードへそのままつながります。
前へ ← 画面用語ガイド次へ → ミッション 1 · プレーヤーを直接表示する
ミッション 1 · プレーヤーを直接表示する
- アプリ起動時に
Shoplive.initializeが呼ばれているか確認します (クイックスタート)。 - 視聴への入口ボタンのアクションでプレーヤーを生成して表示します。
- (任意)デリゲートを接続してイベントを受け取ります — ミッション 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");
}
ShoplivePlayer(context).intent(campaignKey = …) が Intent を返します。自分で startActivity で表示すれば OK です。完了確認
- ボタンタップから 2〜3 秒以内に映像が再生される
- 配信中のキャンペーンであれば、チャット・ライブバッジが表示される (画面用語 ②③)
- 再生失敗時に
errorイベントのcode/messageがログに出力される (エラー処理)
関連: PIP · イベント次へ → ミッション 2 · ディープリンクで表示する
ミッション 2 · ディープリンクでプレーヤーを表示する
myapp://live?campaign=XXXX リンクをタップ → アプリが開いてすぐ再生SDK に専用のディープリンク API があるわけではなく、ディープリンクから campaignKey をパースして ミッション 1 と同じように再生します。
- OS/フレームワークのディープリンクハンドラで URL を受け取ります。
- クエリから
campaignKey(必要に応じてreferrer— ShoplivePlayOptions)を取り出します。 - 初期化・ユーザー設定(ミッション 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 · ユーザー情報の連携
ミッション 3 · ユーザー情報の連携
setUser はいまの視聴者が誰かを Shoplive に伝えるだけで、チャット・視聴・統計がその会員に紐づきます。Shoplive はユーザーの資格情報を確認しません。token では、その情報が加盟店サーバーが発行し改ざんされていないか(署名)を Shoplive が確認でき、profile ではその確認なしに情報をそのまま渡します。ステップ 1 · ログイン状態で選ぶ — 推奨の設定
| ユーザー | 推奨 | 理由 |
|---|---|---|
| 未ログイン | 何も呼び出さない | ゲスト識別子はサーバーが発行します。guest は何もしないため、ログインしていたユーザーをゲストに戻すときは logout() を使います |
| ログイン (推奨) | token — サーバーが署名した JWT | 改ざんできないため、会員ランク別の入場制限・購入連携・正確な視聴者統計に使えます |
| ログイン(バックエンド作業が難しい場合) | profile — id・名前などを直接渡す | SDK が署名なしの JWT を作ります(シンプル認証)。識別・統計には十分ですが、権限の検証には使わないでください |
| ログアウト・ユーザー情報のリセット | Shoplive.logout() | 設定したユーザー情報(JWT・プロフィール)を消去し、配信用トークンもあわせて消去します。accessKey・ゲスト識別子・UTM は保持されます。ユーザー情報だけを消去する呼び出しはありません(guest は何もしません) |
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();
}
token)を使用してください。salt のポリシーは Shoplive チームと合意した値を推奨します。ステップ 4 · セキュア認証用の JWT を作る(顧客企業のバックエンド)
token 認証に使う JWT は、Shoplive が発行した Secret Key で顧客企業のバックエンドが署名します。アプリはログイン直後にバックエンドから JWT を受け取り、setUser(.token(jwt:)) に渡すだけです。
- Shoplive の担当者から Secret Key を受け取ります — base64 でエンコードされた文字列です
- ログインしたユーザー情報で下記の payload を作り、HS256 で署名します
- アプリから要求されるたびに(再生の直前ごとに)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();
profile)の JWT は署名がないため、権限の検証には使えません。完了確認
- ログイン後の視聴時に、チャットのニックネームが会員名で表示される
- Shoplive 管理画面 の統計で該当ユーザー ID(ハッシュ)が照会できる
- ログアウト →
Shoplive.logout()でユーザー情報をリセットしたあとはゲストとして視聴される
前へ ← ミッション 2次へ → ミッション 4 · 画面に埋め込む (プレビュー)
ミッション 4 · 画面に埋め込む (プレビュー)
フィードやリストに入れるプレーヤーはプレビューとして表示します。サーバーが提供する低解像度ストリームを受け取るため、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 はオンになります |
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 ひとつだけです。- Webview — リストを描画し、タップ時にネイティブへ
campaignKeyを渡す - ネイティブ — 渡された値を受け取る
- ネイティブ — 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>
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 · ネイティブで再生
},
);
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:
bindLifecycleを接続) - タップすると本視聴が本解像度で開く
- (ハイブリッド連携) Web のプレビュータップがアプリのネイティブプレーヤーにつながる
前へ ← ミッション 3次へ → ミッション 5 · PIP の適用
ミッション 5 · PIP の適用
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" も追加
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
| フィールド | 値 | 意味 |
|---|---|---|
presentationStyle | FULLSCREEN(既定)/ 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 · イベント · 商品 · クーポン
ミッション 6 · イベント · 商品 · クーポンの連携
デリゲートの構造 — メソッドはたった 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; } // 未知・不正な値はここへ — ストリームは途切れません
| case | payload | いつ届くか | 主な用途 |
|---|---|---|---|
stateChanged | ShoplivePlayerState | プレーヤーのライフサイクル変化 (idle → loading → playing…)。in-App PIP への移行/復帰(inAppPIP)もこのイベントで通知 | 再生状態に応じたアプリ UI の更新 — ミッション 5 |
campaignStatusChanged | ShopliveCampaignStatus | 配信ステータスの変化: ready(待機) → live(配信中) → ended(終了) | ライブバッジなど LIVE限定 の独自 UI のトグル — 画面用語 |
campaignInfoReceived | ShopliveCampaignInfo | キャンペーンのメタデータ(タイトル・ブランドなど)の取得完了時に 1 回 | 独自 UI へのキャンペーン情報の表示 |
playback | PlaybackEvent (Android ShoplivePlaybackEvent) | 再生パイプラインのイベント: started / rendering / rebufferingStarted / rebufferingEnded / ended など。iOS は pb.event、Android はサブタイプで区別 | ローディングスピナー・バッファリング案内の表示 |
connectionStateChanged | ShopliveConnectionState | 接続状態の変化 (connecting / connected / reconnecting / disconnected) | ネットワーク接続状態の案内 |
userNameUpdateRequested | [String: Any] | 視聴者がオーバーレイでニックネームの変更をリクエストしたとき | アプリのプロフィール変更画面への接続 — ミッション 3 |
analytics | ShopliveAnalyticsInfo | 視聴統計イベントの発生時 (campaignKey · isPlaying · isMuted · durationMs · campaign/brand) | 自社アナリティクス(GA/Amplitude など)への連携 |
error | ShopliveError | 復旧不能なエラーでセッションが終了するときのみ。自動復旧中の問題は届きません | エラー案内 + リトライ UI — エラー処理 |
webCommand | name + 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"});
完了確認
- 商品タップ → 自社の詳細ページが開く (プレーヤーは
actionOnNavigationのポリシーに従って PIP/維持/終了 — ミッション 5) - クーポンタップ → 発行処理の後、オーバーレイに成功表示が出る
- 配信終了時に
campaignStatusChanged(ended)が受信される
前へ ← ミッション 5次へ → ミッション 7 · UI カスタマイズ
ミッション 7 · UI カスタマイズ & 配置の変更
カスタマイズは 2 種類 — SDK のアップデートなしで可能
| 種類 | 誰が変更するか | 変更できる範囲 | 反映方法 |
|---|---|---|---|
| ネイティブのカスタマイズ | 加盟店 — アプリのコードで直接 | 標準の外観オプション(下の方法 ①)· 既定のオーバーレイを隠して自作した UI(方法 ②) | 次のアプリ配布で反映 — 既に使っている SDK の API で行うため SDK のアップデートは不要 |
| Web によるカスタマイズ | Shoplive | プレーヤーのオーバーレイ(チャット・商品・お知らせなど)は Shoplive が提供する Web レイヤーのため、デザイン・配置を Shoplive 側で変更します | Shoplive 側で反映 — SDK のアップデートもアプリ配布も不要。ご要望は Shoplive チームにご相談ください |
方法 ① 標準の外観オプション — appearance
ローディングの色・ステータスバー・チャットのフォントなどは設定だけで変更できます。要素の表示/スタイルレベルの変更であれば、この方法で十分です。
| オプション | iOS | Android |
|---|---|---|
| ローディングインジケーターの色 (画面用語 ⑧) | indicatorColor: UIColor | indicatorColor: @ColorInt Int |
| ローディングアニメーション | loadingAnimationImages: [UIImage]? | loadingAnimation: @DrawableRes Int |
| ステータスバーの表示 | — | isStatusBarVisible |
| チャット入力フォント (③) | chatInputFont: UIFont? | chatInputTypeface: Typeface |
| 画面キャプチャの許可 | allowScreenCapture | allowScreenCapture |
方法 ② 要素の再配置 — 標準 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 をアプリが描画します

完了確認
- ブランドの色/フォントがローディング・チャットに反映される (方法 ①)
hiddenの状態でも商品タップ・クーポンのイベントがデリゲートに届き続ける (方法 ②)- 配信ステータス(
ready/live/ended)に応じて独自 UI が正しくトグルされる (画面用語)
前へ ← ミッション 6次へ → ミッション 8 · 番組の配信
ミッション 8 · 番組の配信
カメラプレビュー・配信開始/終了・チャット・設定・カメラ切替/マイク/ズームまで、スタジオ 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; }
| case | payload | いつ届くか | 主な用途 |
|---|---|---|---|
stateChanged | ShopliveBroadcastState | 配信ライフサイクルの変化: idle → preview(カメラプレビュー) → connecting → live(配信中) → ended。ネットワークの瞬断時に reconnecting → live と自動復帰する様子もこのイベントで観測できます | アプリの画面遷移・配信中表示などのステータス連携 |
error | ShopliveError | 復旧不能なエラーで配信が終了するときのみ (例: トークン無効 — ステップ 1)。再接続などの自動復旧中には届きません | エラー案内 + リトライ UI — エラー処理 |
ShopliveBroadcastState(型辞典): idle → preview → connecting → live → (reconnecting) → ended。
配信終了のポリシー(残り時間・終了中の入場許可など)は CampaignEndOption で維持されます。
完了確認
- スタジオに入るとカメラプレビューが表示される (
previewステータス) - 配信開始 → 視聴アプリ(ミッション 1)で該当キャンペーンが LIVE として再生される
- ネットワークの瞬断時に
reconnecting → liveで自動復帰する
前へ ← ミッション 7次へ → データフィールド辞典
データフィールド辞典
公開 API の設定オブジェクトをフィールド単位で整理しました。すべてのフィールドに既定値があるため、必要なものだけ指定すれば十分です。
ShopliveConfiguration — 初期化 (アプリで 1 回)
利用箇所: クイックスタート · ミッション 2 (コールドスタート)
| フィールド | 型 | 既定値 | 説明 |
|---|---|---|---|
accessKey | String | 必須 | 顧客企業を識別するキー — Shoplive が発行(各キーの役割) |
allowedWebViewDomains | [String] | 空配列 (全許可) | オーバーレイ WebView の許可ドメイン。必須ドメインは SDK が自動的に含みます (導入企業の設定) |
attribution | ShopliveAttribution? | なし | 流入トラッキングのまとまり (下記) |
appVersion (Android) | String? | なし | 3.0.3 で削除 — SDK がアプリのバージョンを自分で読み取るため、渡しません(Flutter 3.0.3 プラグインには残っていますが、値は無視され警告ログだけが出ます) |
ShopliveAttribution — 流入トラッキング
| フィールド | 型 | 説明 |
|---|---|---|
utmSource / utmCampaign / utmMedium / utmContent | String? | UTM 4 種 |
advertisingId (iOS) | String? | 広告識別子 (IDFA) — 収集・受け渡しはアプリの責任。3.0.3 時点では iOS のみ |
ShopliveUser — ユーザー情報 (3 形態)
利用箇所: ミッション 3 · ユーザー情報の連携 (判断基準・ハッシュ化・Secret Key を含む)
| 形態 | フィールド | 説明 |
|---|---|---|
token | jwt: String | サーバー署名 JWT (推奨) — 発行方法 |
profile | id(必須) · 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)
| グループ | フィールド | 既定値 | 説明 |
|---|---|---|---|
| — | type | live | 再生形態 — preview はサーバーが提供するプレビューストリーム(低解像度)で受け取ります(ミッション 4) |
pipミッション 5 | isInAppPipEnabled | true | in-App フローティング PIP の許可 (VC/Activity・View 共通) |
isOSPipEnabled | true | OS PIP — バックグラウンドに入ると自動 (Android は Activity 方式専用) | |
defaultPosition | bottomRight | in-App PIP の既定の角の位置 (ShoplivePipPosition) | |
scale | 0.4 | in-App PIP のサイズ倍率 | |
padding | 0 | 画面端の余白 | |
fixedWidth (iOS) | なし | in-App PIP ウィンドウの幅を固定(pt) — 指定すると scale の代わりに使用 | |
floatingOffset (iOS) | 0 | in-App PIP の位置に加える追加オフセット | |
windowProvider (iOS) | なし | in-App PIP を表示する UIWindow を指定(未指定なら自動で探索) | |
enterOSPipOnBackPressed (Android) | false | 戻る操作で OS PIP に切り替え(Activity 方式のみ) | |
aspectRatio (Android) | RATIO_9X16 | in-App PIP の縦横比 (ShoplivePipRatio) | |
isDragOutToCloseEnabled | true | in-App PIP ウィンドウを画面外にドラッグすると閉じる。false なら元の位置に戻り、閉じるボタン・close() でのみ閉じます (3.0.3+) | |
sound | muteOnStart | false | 開始時にミュート |
mixWithOthers | false | 他アプリの音声と同時に再生 | |
autoResumeOnCallEnded (iOS) / autoResumeOnFocusGained (Android) | true | 通話終了/フォーカス回復時に自動再開 | |
isVolumeKeyEnabled (Android) | type による | 音量キーでの調整を許可 — 指定しない場合、PREVIEW はオフ、LIVE はオン | |
appearanceミッション 7 | indicatorColor | white | ローディングインジケーターの色 |
resizeMode (iOS) | fill | 映像フィットの初期値 (fill / fit)。再生中はプレーヤーの resizeMode で変更します(Android はプレーヤーのプロパティのみ) | |
loadingAnimationImages (iOS) / loadingAnimation (Android) | なし | カスタムのローディングアニメーション | |
isStatusBarVisible (Android) | true | ステータスバーの表示 | |
chatInputFont (iOS) / chatInputTypeface (Android) | システム | チャット入力フォント (画面用語 ③) | |
chatSendButtonFont (iOS) | システム | チャット送信ボタンのフォント | |
allowScreenCapture | true | 画面キャプチャ/録画の許可 | |
navigationミッション 6 | actionOnNavigation | pip | 商品ランディングなど画面遷移時のプレーヤーの動作: pip / keep / close |
shareScheme | なし | 共有ボタンが使用するアプリの URL スキーム (画面用語 ⑥) | |
closeWhenAppDestroyed (Android) | true | アプリ終了時にプレーヤーも閉じる | |
overlayミッション 7 | ui | builtIn | 標準オーバーレイ UI の表示(builtIn) / 非表示(hidden、コマンドチャネルは維持) |
| — | customParameters | 空マップ | 導入企業が定義した key-value をキャンペーンへ渡す (導入企業の設定) |
ShoplivePlayOptions — 再生 1 回ごとのオプション
利用箇所: ミッション 1 · ミッション 2 (ディープリンクの referrer)
| フィールド | 型 | 既定値 | 説明 |
|---|---|---|---|
referrer | String? | なし | 流入経路の識別子 (ディープリンクの ref など) — 統計に反映 |
keepWindowStateOnPlayExecuted | Bool | false | 再生開始時に既存のウィンドウ状態(PIP など)を維持 |
presentationStyle | ShoplivePlayerPresentationStyle | fullScreen | 開始時の表示 — pip ならフルスクリーンを経ずに in-App PIP で開始します(pip.isInAppPipEnabled = true が必要)。プリセット ShoplivePlayOptions.preview(Android PREVIEW)がこの値を使います |
型 & ステータス
両プラットフォームで同型です(iOS は小文字の case、Android は大文字の enum)。
| 型 | 値 | 意味 · 利用箇所 |
|---|---|---|
ShoplivePlayerType | live · preview | 再生形態 — preview はプレビューストリーム(低解像度)— ミッション 4 |
ShoplivePlayerPresentationStyle | fullScreen · pip (Android FULLSCREEN · PIP) | 再生開始時の表示 — ShoplivePlayOptions · ミッション 5 |
ShoplivePlayerState | idle · loading · playing · paused · inAppPIP(IN_APP_PIP) · closed | プレーヤーのライフサイクル — ミッション 1・5 |
ShopliveBroadcastState | idle · preview · connecting · live · reconnecting · ended | 配信のライフサイクル — ミッション 8 |
ShopliveCampaignStatus | ready · live · ended | キャンペーン(配信)のステータス — LIVE限定 要素の基準 (画面用語) |
ShopliveConnectionState | connecting · connected · reconnecting · disconnected | 接続状態 — ミッション 6 |
ShopliveResizeMode | fill · fit | 画面のフィット — ミッション 1 |
ShoplivePipPosition | top · middle · bottom × Left · Center · Right(9 か所、例: bottomRight) | in-App PIP の既定位置 — ミッション 5 |
ShopliveOverlayUIMode | builtIn · hidden | 標準オーバーレイ UI の表示/非表示 (コマンドチャネルは維持) — ミッション 7 |
ShopliveNavigationAction | pip · 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 の受け渡し |
トラブルシューティング & 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)を使用してください。