Shoplive ショートフォーム SDK 連携ガイド
コマース向けショート動画をアプリに組み込む方法を、実装したいケース(ミッション)単位でご案内します。
フルスクリーン再生・コレクションの組み込み・フローティングプレビュー・商品/シェア連携まで、必要な方式だけ選んで適用できます。
コード例は上部の iOS / Android タブで切り替えられます。
概要 & 要件
Shoplive ショートフォーム SDK は、コマースに最適化された縦型ショート動画をアプリに組み込むための SDK です。 再生・プリロード・無限スクロール・商品オーバーレイ・いいね/コメント/ブックマークといった UI は SDK がまとめて提供し、 アプリはどのショートを表示するか(検索条件)とタップしたときにどこへ遷移させるか(ルーティング)だけを担当します。
モジュール構成
ミッションマップ — 何を実装したいですか?
| 実装したいこと | ミッション |
|---|---|
| ボタンを押すとショートがフルスクリーンで開き、上下にスワイプできる | ミッション 1 · フルスクリーン再生 |
| ホームや特集ページの中にショートのコレクションを置く | ミッション 2 · コレクションを組み込む |
| 商品詳細に関連ショートを小さなウィンドウで表示する | ミッション 3 · プレビュー表示 |
| ログイン会員としていいね・コメント・ブックマークできるようにする | ミッション 4 · ユーザー情報の連携 |
| 商品タップで自社の詳細ページへ/シェアを自社のシートで出す | ミッション 5 · イベント・商品・シェア |
| 自社サーバーが決めた順序・リストのまま再生する(パーソナライズ) | ミッション 6 · 自前のリストで再生 |
| いいね・シェアボタンを隠す、カードの表示項目を変える | ミッション 7 · UI カスタマイズ |
提供形態(プラットフォーム対応)
| 形態 | iOS | Android |
|---|---|---|
| フルスクリーン再生 | ShopLiveShortform.play(requestData:) | ShopLiveShortform.play(context, collectionData) |
| コレクション(カード/縦/横) | CardTypeViewBuilder · ListViewBuilder → getView() | ShopLiveShortformCardTypeView などの XML View |
| 画面内フルフィード | ShopLiveShortsCollectionView(UIView) | ShopLiveShortformDetailTypeView(XML View) |
| フローティングプレビュー | ShopLiveShortform.showPreview(requestData:) | ShopLiveShortform.showPreview(activity, data) |
| イベント受信 | ShopLiveShortformReceiveHandlerDelegate | ShopLiveShortformHandler |
build(...).getView())、Android は XML に置いたビューを設定して submit() します。
検索条件(タグ・ブランド・SKU・グループ)とイベントモデルは両プラットフォームで同型なので、
一方で覚えた考え方はそのままもう一方に移せます。要件
| 項目 | iOS | Android |
|---|---|---|
| 最小バージョン | iOS 11.0 以上 | shoplive-media3(既定エンジン)利用時は minSdk 23 以上 · shoplive-exoplayer 利用時は 19 以上 |
| 言語 / ツールチェーン | Swift 5 | Kotlin · JDK 17. compileSdk 36 |
| SDK バージョン | 1.8.13(ShopliveShortformSDK) | 1.8.14(shoplive-short-form) |
| 必須の同梱モジュール | ShopliveSDKCommon(common-ios) | shoplive-common · shoplive-network · 再生エンジン |
インストール
iOS
// 1) Swift Package Manager
// Xcode ▸ File ▸ Add Package Dependencies…
dependencies: [
.package(url: "https://github.com/shoplive/shortform-ios", .upToNextMajor(from: "1.8.13")),
.package(url: "https://github.com/shoplive/common-ios", .upToNextMajor(from: "1.8.13")),
]
// 2) CocoaPods — Podfile
// pod 'ShopliveShortformSDK', '1.8.13'
// pod 'ShopliveSDKCommon', '1.8.13'
// 3) import
import ShopliveSDKCommon // accessKey・ユーザー認証・共通モデル
import ShopLiveShortformSDK // ショートの再生・一覧・プレビュー
// settings.gradle / build.gradle — mavenCentral() が必要
dependencies {
implementation("cloud.shoplive:shoplive-short-form:1.8.14") // ショート
implementation("cloud.shoplive:shoplive-common:1.8.14") // 共通(accessKey・認証)
implementation("cloud.shoplive:shoplive-network:1.8.14") // ネットワーク
// 再生エンジン — 以下の<b>どちらか一方だけ</b>を宣言してください(エンジンのバージョン体系は SDK と別です)
implementation("cloud.shoplive:shoplive-media3:1.10.1.3") // media3(既定)— minSdk 23
// implementation("cloud.shoplive:shoplive-exoplayer:2.19.1.20") // ExoPlayer(レガシー)— minSdk 19
}
android {
defaultConfig { multiDexEnabled = true }
}
shoplive-short-form は共通・ネットワーク・再生エンジンを compileOnly で参照しています。
つまりアプリモジュールで宣言しないと実行時にクラスが見つかりません。
既定は shoplive-media3、レガシー ExoPlayer を使うアプリでは shoplive-exoplayer を入れてください。各クラスの提供元
| クラス | モジュール(iOS) | パッケージ(Android) |
|---|---|---|
ShopLiveCommon · ShopLiveCommonError | ShopliveSDKCommon | cloud.shoplive.sdk.common |
ShopLiveShortform・コレクション・プレビュー | ShopLiveShortformSDK | cloud.shoplive.sdk.shorts |
| 商品・ショートのレスポンスモデル | ShopliveSDKCommon(ProductData など) | cloud.shoplive.sdk.network.response |
クイックスタート (Quick Start)
インストールが終わったら、以下のコードで最初のショートを再生できます。
accessKey は Shoplive が発行します。お持ちでない場合は Shoplive 担当者または管理コンソールでご確認ください。
ショートにキャンペーンキーは不要で、検索条件(タグ・ブランド・SKU・グループ ID)で再生対象を決めます。フルスクリーン再生 — 2 行
// 1) アプリ起動時に 1 回(AppDelegate)
ShopLiveCommon.setAccessKey(accessKey: "取得した_ACCESS_KEY")
// 2) 再生 — 条件なしで呼ぶとすべてのショート
ShopLiveShortform.play(requestData: ShopLiveShortformCollectionData(delegate: nil))
// 1) アプリ起動時に 1 回(Application.onCreate)
ShopLiveCommon.setAccessKey("取得した_ACCESS_KEY")
// 2) 再生 — 条件なしで呼ぶとすべてのショート
ShopLiveShortform.play(context)
この 2 行でショートコレクションを読み込み、縦型フルスクリーンで再生します。商品カード・いいね・コメント・シェアのオーバーレイも動きます。 詳細: 1 ユーザー情報の連携 · 2 ミッション 1。
setAccessKey が必ず先に実行されている必要があります。
未実行だと 9000 NOT_INITIALIZED_ACCESS_KEY が返ります(エラー処理)。
AppDelegate / Application.onCreate での実行を推奨します。条件を付けて再生
let data = ShopLiveShortformCollectionData(
tags: ["春の新作", "アウター"],
tagSearchOperator: .AND, // すべてのタグを満たす(.OR = いずれか)
brands: ["shoplive"],
shuffle: true,
skus: ["SKU-1001"],
shortsCollectionId: "COLLECTION_ID",
isMuted: false,
delegate: self // イベント受信 — ミッション 5
)
ShopLiveShortform.play(requestData: data)
val data = ShopLiveShortformCollectionData().apply {
tags = listOf("春の新作", "アウター")
tagSearchOperator = ShopLiveShortformTagSearchOperator.AND
brands = listOf("shoplive")
shuffle = true
skus = listOf("SKU-1001")
shortsCollectionId = "COLLECTION_ID"
isMuted = false
handler = myHandler // イベント受信 — ミッション 5
}
ShopLiveShortform.play(context, data)
次のステップ画面用語ガイド · ミッション 1 から始める →
画面用語ガイド
ここでは画面上の各要素の呼び名を整理します。以降のミッション・オプション・イベントの説明でも同じ用語を使います。 認証が必要は、ログインユーザーとして識別されて初めて動作する要素です(ミッション 4)。
詳細画面(フルスクリーン再生)
上下スワイプで送る縦型フルスクリーンです。ミッション 1・3・6 はいずれもこの画面につながります。
| 番号 | 要素 | 表示条件 | 説明・関連 API |
|---|---|---|---|
| ① | 映像エリア | 常時 | 縦型動画。表示方式は setResizeMode(CENTER_CROP/FIT/AUTO)— ミッション 7 |
| ② | クリエイター・ブランド | 常時 | コンソールで設定した投稿者・ブランド情報 |
| ③ | タイトル・説明・ハッシュタグ | 常時 | shortsDetail.title / description / tags |
| ④ | 商品カード・商品バナー | 常時 | タップでアプリへコールバック — ミッション 5(handleProductItem / getOnClickProductListener) |
| ⑤ | いいね | 認証が必要 | isLikeButtonVisible で非表示にできます — ミッション 7 |
| ⑥ | コメント | 認証が必要 | isCommentButtonVisible |
| ⑦ | ブックマーク | 認証が必要 | isBookmarkVisible |
| ⑧ | シェア | 常時 | タップするとアプリ側でシェアシートを表示します — ミッション 5(handleShare / onShare) |
| ⑨ | ミュート切り替え | 常時 | 初期値は isMuted。VIDEO_MUTED/VIDEO_UNMUTED を通知 |
| ⑩ | 閉じる(戻る) | 常時 | アプリから閉じるには ShopLiveShortform.close() |
コレクション(アプリ内への組み込み)
既存画面に組み込むコレクションです。カードに何を表示するかはオプションで切り替えます(ミッション 7)。
| タイプ | 見た目 | 使いどころ |
|---|---|---|
カード型(CardType) | グリッド。列数を調整可能 | 特集・ブランド専用エリア |
縦型(Vertical) | 下へ続くコレクション | タブ全体をショートにする |
横型(Horizontal) | 横にスワイプするカルーセル | ホームフィードの帯 |
フルフィード(DetailType / ShortsCollectionView) | 詳細画面をそのままビューに | タブ全体がショートのアプリ |
| カード内の要素 | 既定値 | オフにする方法 |
|---|---|---|
| サムネイル/自動再生 | 再生 ON | disablePlayVideos() |
| タイトル | 表示 | setVisibleTitle(false) |
| 説明 | 表示 | setVisibleDescription(false) |
| ブランド | 表示 | setVisibleBrand(false) |
| 商品数 | 表示 | setVisibleProductCount(false) |
| 再生数 | 表示 | setVisibleViewCount(false) |
プレビュー(フローティング)
別の画面の上に浮かぶ小さなウィンドウです。タップすると詳細画面に拡大します(ミッション 3)。
| 要素 | 説明・関連 API |
|---|---|
| 位置 | 四隅のいずれか — previewPosition / position(既定 bottomRight) |
| サイズ・角丸 | iOS previewScale · previewRadius / Android width · height · radius |
| 余白 | iOS previewEdgeInset / Android marginTop ほか 4 方向 |
| 閉じるボタン | useCloseButton(既定は表示) |
| スワイプで閉じる | iOS enableSwipeOut / Android setEnabledSwipeOut |
| タップ時の動作 | 既定は詳細画面へ拡大。iOS は useCustomAction + clickEventCallBack、Android は setUseCustomAction + setOnClickListener でアプリ側が横取りできます |
前へ ← クイックスタート次へ → デモアプリで確認する
デモアプリで確認する
SDK に同梱されるデモアプリは、本ガイドのミッションと同じケースメニュー(ミッションマップ)で構成されています。 コードを書く前にデモで動きを見ておくと理解が早くなります。
- 既定モード — Shoplive デモ設定: インストール直後、入力なしですべてのケースをデモデータで確認できます。
- 自社情報モード — 導入企業の設定を入力: 設定画面に取得済みの
accessKeyと(任意で)ユーザー JWT を入力すると、 自社のショートと自社ユーザーで同じケースを体験できます。 - オプションダイアログでタグ・ブランド・SKU・シャッフル・カードタイプ・プレビュー位置を変えながら、 ミッション 7 のオプションが画面にどう反映されるか確認できます。
前へ ← 画面用語ガイド次へ → ミッション 1 · フルスクリーンで再生する
ミッション 1 · フルスクリーンで再生する
- アプリ起動時に
setAccessKeyが呼ばれているか確認します(クイックスタート (Quick Start))。 - 入口ボタンのアクションで検索条件を組み立て、
playを呼びます。 - (任意)デリゲート/ハンドラーを接続して商品・シェアのイベントを受け取ります — ミッション 5。
@IBAction func openShorts() {
let data = ShopLiveShortformCollectionData(
tags: ["春の新作"],
tagSearchOperator: .AND,
shuffle: true,
delegate: self // 任意: イベント受信
)
ShopLiveShortform.play(requestData: data)
}
fun openShorts() {
val data = ShopLiveShortformCollectionData().apply {
tags = listOf("春の新作")
tagSearchOperator = ShopLiveShortformTagSearchOperator.AND
shuffle = true
handler = myHandler // 任意: イベント受信
}
ShopLiveShortform.play(context, data)
}
shortsId を一緒に渡すと、そのショートが最初の画面として開き、残りが後ろに続きます。
プッシュやディープリンクで特定のショートを開くケースがこれにあたります。ShopLiveShortform.intent(context, data) が Intent を返します。
自分で startActivity を呼べば、トランジションやフラグをアプリ側で決められます。閉じる
ShopLiveShortform.close() // アプリ側から閉じるとき(ログアウト、ディープリンクなど)
ShopLiveShortform.close() // アプリ側から閉じるとき(ログアウト、ディープリンクなど)
完了確認
- タップから 1〜2 秒で動画が再生される
- 上下スワイプで前後のショートに移動する
- 商品が登録されたショートでは下部に商品カードが表示される(画面用語 ④)
- 条件を誤って結果が 0 件ならデータなしの案内が出る — 条件を絞りすぎていないか確認
関連: イベント · UI オプション次へ → ミッション 2 · コレクションを組み込む
ミッション 2 · コレクションを画面に組み込む
コレクションには3 つの形があります — カード型(グリッド)・縦型・横型(カルーセル)。 3 つとも同じ検索条件と同じ表示オプションを使うので、形を選ぶだけです。
ステップ 1 · ビューを作って配置する
// iOS — ビルダーで作り、getView() で取り出して配置します
private var builder: ShopLiveShortform.CardTypeViewBuilder?
override func viewDidLoad() {
super.viewDidLoad()
let builder = ShopLiveShortform.CardTypeViewBuilder()
let listView = builder.build(
cardViewType: .type1,
listViewDelegate: self,
shortsCollectionDelegate: self, // 商品・シェアのイベント — ミッション 5
enableSnap: false,
enablePlayVideo: true,
playOnlyOnWifi: false,
cellSpacing: 8,
cellRadius: 16,
viewCountVisibility: true
).getView()
listView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(listView)
// … Auto Layout の制約 …
builder.setHashTags(tags: ["春の新作"], tagSearchOperator: .AND)
builder.submit() // ← 条件を決めたら必ず呼ぶ
self.builder = builder
}
// Android — XML にビューを置き、コードで設定してから submit()
// res/layout/fragment_home.xml
// <cloud.shoplive.sdk.shorts.ShopLiveShortformCardTypeView
// android:id="@+id/shortsCardTypeView"
// android:layout_width="match_parent"
// android:layout_height="0dp" />
with(binding.shortsCardTypeView) {
setSpanCount(2) // グリッドの列数
setViewType(ShopLiveShortform.CardViewType.CARD_TYPE1)
handler = myHandler // 商品・シェアのイベント — ミッション 5
setHashTags(listOf("春の新作"), ShopLiveShortformTagSearchOperator.AND)
submit() // ← 条件を決めたら必ず呼ぶ
}
listViewDelegate は必須メソッドが 2 つあります(iOS)
ShopLiveShortformListViewDelegate は NSObject を継承する必要があり、
onListViewError(error:) と onShortsSettingsInitialized() はどちらも optional ではありません。
どちらか欠けるとコンパイルできません。onShortsSettingsInitialized() は CardTypeViewBuilder で作ったカード型ビューでのみ呼ばれます。
extension MyViewController: ShopLiveShortformListViewDelegate {
func onListViewError(error: Error) {
// 一覧の取得に失敗 — 再試行 UI など
}
func onShortsSettingsInitialized() {
// サーバー設定の受信完了 — 以降にオプションを再適用すると安全です
}
}submit() を忘れるとコレクションは空のままです
検索条件(setHashTags・setBrands・setSkus・setShortsCollectionId)は
設定しただけでは反映されません。最後に submit() を呼んで初めてサーバー検索が走ります。
条件を変えて読み直すときも同じです(iOS では reloadItems() も使えます)。ステップ 2 · 形を選ぶ
// 縦型・横型は ListViewBuilder で
let builder = ShopLiveShortform.ListViewBuilder()
let listView = builder.build(
cardViewType: .type1,
listViewType: .horizontal, // .vertical | .horizontal
playableType: .CENTER, // 横型でどのセルを再生するか
listViewDelegate: self,
shortsCollectionDelegate: self
).getView()
builder.submit()
// 縦型 — ShopLiveShortformVerticalTypeView
with(binding.shortsVerticalTypeView) {
setSpanCount(2)
setViewType(ShopLiveShortform.CardViewType.CARD_TYPE1)
submit()
}
// 横型 — ShopLiveShortformHorizontalTypeView
with(binding.shortsHorizontalTypeView) {
setViewType(ShopLiveShortform.CardViewType.CARD_TYPE1)
setPlayableType(ShopLiveShortform.PlayableType.CENTER)
enableSnap()
submit() // ← 横型も submit() が必要です
}
submit(collectionData, response) はすでに取得済みのレスポンスを入れる形です。
ShopLiveShortform.get(context, collectionData, listener) で先に取得してから渡してください —
ホーム画面で複数のカルーセルを1 回の取得で埋めたいときに有効です。ステップ 3 · 画面遷移時の再生制御
コレクションの自動再生は、画面が見えていないときに止めるとバッテリーと通信量を節約できます。
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
builder?.enablePlayVideos()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
builder?.disablePlayVideos()
}
override func viewWillTransition(to size: CGSize, with c: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: c)
builder?.notifyViewRotated() // 回転時にレイアウトを再計算
}
override fun onResume() {
super.onResume()
binding.shortsCardTypeView.enablePlayVideos()
}
override fun onPause() {
super.onPause()
binding.shortsCardTypeView.disablePlayVideos()
}
ステップ 4(任意)· 画面全体をショートフィードにする
フルスクリーンを別に開く代わりに、アプリのタブをまるごとショートフィードにできます。
let feed = ShopLiveShortsCollectionView(
requestData: ShopLiveShortformCollectionData(tags: ["春の新作"], delegate: self)
)
view.addSubview(feed)
feed.resultHandler = { result in
if case .didScrollToShortsId(let shortsId) = result {
print("現在のショート: \(shortsId ?? "")")
}
}
feed.action(.play) // .pause · .setMuted(true) · .setScrollEnabled(false) など
// res/layout — <cloud.shoplive.sdk.shorts.ShopLiveShortformDetailTypeView … />
with(binding.shortsDetailTypeView) {
setMuted(false)
setUIComponentPadding(bottom = bottomNavHeight) // 下部タブバーに隠れないように
setHashTags(listOf("春の新作"), ShopLiveShortformTagSearchOperator.AND)
submit() // ← 条件を確定したら必ず呼ぶ
}
// システムの戻る操作をフィードに伝える
override fun onBackPressed() {
binding.shortsDetailTypeView.onBackPressed()
}
完了確認
- コレクションにサムネイルが並び、見えているセルが自動再生される
- カードをタップするとフルスクリーン詳細につながる
- 別タブに移動すると再生が止まる(
disablePlayVideos) - 条件を変えて
submit()するとコレクションが入れ替わる
関連: 表示項目を隠す次へ → ミッション 3 · プレビュー表示
ミッション 3 · プレビューを表示する
プレビューは商品とショートをつなぐ導線に使います。商品詳細に来たユーザーに、 その商品が登場するショートを小窓で見せ、興味を示したらフルスクリーンに上げる流れです。
- 商品画面に入るときに
productIdまたはskusで関連ショートを指定します。 - 位置・サイズ・閉じるボタンなどの表示オプションを決めます。
- 画面を離れるときにプレビューを閉じます。
let preview = ShopLiveShortformPreviewData(
productId: "PRODUCT_ID",
isMuted: true,
previewPosition: .bottomRight,
previewRadius: 12,
previewEdgeInset: .init(top: 0, left: 0, bottom: 24, right: 16),
useCloseButton: true,
enableSwipeOut: true,
maxCount: 10,
delegate: self
)
ShopLiveShortform.showPreview(requestData: preview)
// 閉じるとき
ShopLiveShortform.close()
ShopLiveShortform.hidePreview() // 重複防止: 表示前に一度閉じる
ShopLiveShortform.showPreview(
activity,
ShopLiveShortformPreviewData().apply {
productId = "PRODUCT_ID"
isMuted = true
setPosition(ShopLivePreviewPositionConfig.BOTTOM_RIGHT)
setRadius(12f)
setMarginBottom(24)
setMarginRight(16)
setUseCloseButton(true)
setIsAutoClosed(true)
maxCount = 10
handler = myHandler
}
)
// 閉じるとき
ShopLiveShortform.hidePreview()
// いま表示中かどうか
if (ShopLiveShortform.isShowingPreview()) { /* … */ }
hidePreview()、iOS は close() です。useCustomAction: true と clickEventCallBack を併せて渡すと、
プレビューのタップ時にフルスクリーンへ拡大せずアプリが望む画面へ遷移させられます。完了確認
- 商品詳細に入ると指定した隅に小窓が出る
- タップするとフルスクリーン詳細に拡大する
- 閉じるボタン(またはスワイプ)で消える
- 別の画面に移動してもプレビューが残らない
関連: 商品タップイベント次へ → ミッション 4 · ユーザー情報の連携
ミッション 4 · ユーザー情報を連携する
setAuthToken・setUser はいまの視聴者が誰かを Shoplive に伝えるだけで、いいね・コメント・ブックマークと視聴履歴がその会員に紐づきます。Shoplive はユーザーの資格情報を確認しません。JWT(setAuthToken)では、その情報が加盟店サーバーが発行し改ざんされていないか(署名)を Shoplive が確認でき、プロフィール(setUser)ではその確認なしに情報をそのまま渡します。ステップ 1 · そもそも必要か — 判断基準
| 状況 | 必要か |
|---|---|
| ショートを見るだけ(再生・スクロール・商品タップ) | 不要 — 未ログインで動作します |
| いいね・コメント・ブックマークを残す | 必要 — ユーザー識別がないと記録されません |
| 会員ごとの視聴履歴やパーソナライズが必要 | 必要 |
ステップ 2 · ログイン状態で選ぶ — 推奨の設定
| ユーザー | 推奨 | 理由 |
|---|---|---|
| 未ログイン | 何も呼び出さない | ゲスト識別子はサーバーが発行します。ログインしていたユーザーをゲストに戻すときは、下記のログアウト処理でユーザー情報をリセットします |
| ログイン (推奨) | setAuthToken — サーバーが署名した JWT | 改ざんできないため、いいね・コメント・ブックマーク・視聴履歴をその会員のものとして信頼して使えます |
| ログイン(バックエンド作業が難しい場合) | setUser — プロフィール(userId・名前など)を直接渡す | 署名のないクライアント値のため偽装できます。識別・統計には十分ですが、権限の検証には使わないでください |
| ログアウト・ユーザー情報のリセット | setAuthToken(nil/null) + setUser(nil/null)(iOS・Android 共通) | 設定したユーザー情報(JWT・プロフィール)を消去して、前のユーザーの記録が混ざらないようにします。名前は setAuthToken ですが、渡す値はユーザー情報(JWT)です |
setAuthToken → ログアウト時にユーザー情報をリセット。// ① ログイン · セキュア認証 (推奨) — バックエンドが署名した JWT (ステップ 4)
ShopLiveCommon.setAuthToken(authToken: myJwtFromServer)
// ② ログイン · 簡易 — プロフィールを直接渡す (署名なし · 権限の検証には使わない)
ShopLiveCommon.setUser(user: ShopLiveCommonUser(
userId: hashedUserId, // 会員 ID の原文ではなくハッシュ値 (ステップ 3)
userName: "山田花子",
age: 29,
gender: .female,
userScore: 3
))
// ③ 未ログイン — 呼び出す必要はありません
// ログアウト — ユーザー情報をリセット(JWT・プロフィールを消去)
ShopLiveCommon.setAuthToken(authToken: nil)
ShopLiveCommon.setUser(user: nil)
// 現在のログイン状態
let isLoggedIn = ShopLiveCommon.isLoggedIn()
// ① ログイン · セキュア認証 (推奨) — バックエンドが署名した JWT (ステップ 4)
ShopLiveCommon.setAuthToken(myJwtFromServer)
// ② ログイン · 簡易 — プロフィールを直接渡す (署名なし · 権限の検証には使わない)
ShopLiveCommon.setUser(
ShopLiveCommonUser(userId = hashedUserId).apply { // 会員 ID の原文ではなくハッシュ値 (ステップ 3)
setUserName("山田花子")
setAge(29)
setGender(ShopLiveCommonUserGender.FEMALE)
}
)
// ③ 未ログイン — 呼び出す必要はありません
// ログアウト — ユーザー情報をリセット(JWT・プロフィールを消去)
ShopLiveCommon.setAuthToken(null)
ShopLiveCommon.setUser(null)
clearAuth() は消去する範囲がプラットフォームごとに異なります Android はユーザー情報(ユーザー・JWT・userId)だけを消去します。iOS は accessKey・ゲスト識別子・UTM を含む共通状態をすべてリセットするため、使う場合は次に使う前に accessKey を設定し直してください。ユーザー情報だけを消去するには、上のように setAuthToken・setUser に nil(null)を渡します。ステップ 3 · ID のハッシュ化 — 会員 ID の平文を露出させない
userId は Shoplive のサーバー・コンソールに保存され、他のユーザーに見えることもある値です。
アプリ内部の会員 ID をハッシュ化して渡し、アプリ側でそのハッシュ値から会員を逆引きできるマッピングを持ってください。アプリとバックエンドが同一の方式でハッシュ化した値を userId として使用します。JWT の userId もプロフィールの userId も同じです。
同じユーザーには常に同じ値が出るよう、一貫した salt・アルゴリズムを使うことが重要です(いいね・ブックマークの保持、統計・再訪の識別)。
ハッシュ化のコード例を見る (Swift · Kotlin)
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) }
}
setAuthToken)を使用してください。salt のポリシーは Shoplive チームと合意した値を推奨します。ステップ 4 · セキュア認証用の JWT を作る(顧客企業のバックエンド)
setAuthToken に渡す JWT は、Shoplive が発行した Secret Key で顧客企業のバックエンドが署名します。アプリはバックエンドから JWT を受け取り、setAuthToken に渡すだけです。
ライブ(Player)SDK と同じ JWT です。
- Shoplive の担当者から Secret Key を受け取ります — base64 でエンコードされた文字列です
- ログインしたユーザー情報で下記の payload を作り、HS256 で署名します
- アプリから要求されるたびに(ショート画面に入る直前ごとに)JWT を新しく発行して返します。アプリに JWT を保存して使い回さないでください
exp まで使えてしまいます。アプリが setAuthToken を呼ぶたびにバックエンドへ新しい 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();
setAuthToken に渡すだけにします。プロフィール(setUser)は署名がないため、権限の検証には使えません。ステップ 5 · 呼び出すタイミング
- アプリのログイン直後 — ショート画面に入る前に設定
- ショート画面に入るたびに — バックエンドで新しく発行した JWT で再度
setAuthToken(保存したトークンを使い回さない) - ログアウト時 — ユーザー情報をリセットして前のユーザーの記録が混ざらないようにする
完了確認
- ログイン後、いいねを押してアプリを再起動しても状態が保持される
- ログイン後、コメント投稿がブロックされない
- ログアウト → ユーザー情報をリセットしてから入り直すと、いいね・ブックマークが初期化される(ゲストとして視聴される)
前へ ← ミッション 3次へ → ミッション 5 · イベント・商品・シェア
ミッション 5 · イベント・商品・シェア
SDK は画面を描くだけで、判断はアプリに委ねます。商品をタップしたときどの画面へ送るか、 シェア時にどんなリンクを作るかはすべてアプリが決めます。その経路がデリゲート(iOS)/ハンドラー(Android)です。
デリゲートの構成 — 大きく 3 種類
| 対象 | iOS | Android |
|---|---|---|
| 商品タップ | handleProductItem(shortsId:shortsSrn:product:) | getOnClickProductListener() |
| 商品バナータップ | handleProductBanner(shortsId:shortsSrn:scheme:) | getOnClickBannerListener() |
| シェアタップ | handleShare(shareMetadata:) · handleShare(shareUrl:) | onShare(context, data) · onShare(context, url) |
| 一般イベント | onEvent(messenger:command:payload:) | onEvent(context, messenger, command, payload) |
| エラー | onError(error:) | onError(context, error) |
| 表示/解除 | onDidAppear() · onDidDisAppear() | onCreate() · onDestroy() |
| ショート切り替え | onShortsAttached/Detached(data:) | onShortsAttached/Detached(data) |
ステップ 1 · 接続する
// データオブジェクトに delegate を持たせて渡します(再生・プレビュー・一覧で共通)
let data = ShopLiveShortformCollectionData(delegate: self)
ShopLiveShortform.play(requestData: data)
extension MyViewController: ShopLiveShortformReceiveHandlerDelegate {
// 必要なものだけ実装 — すべて optional
}
// 先にハンドラーを宣言します
val myHandler = object : ShopLiveShortformHandler() {
// 必要なものだけ override
}
// ① データ/ビューごとに指定 — こちらが基本です
val data = ShopLiveShortformCollectionData().apply { handler = myHandler }
binding.shortsCardTypeView.handler = myHandler
// ② グローバル — ハイブリッド(WebView)ブリッジ経路でのみ読まれます
ShopLiveShortform.setHandler(myHandler)
ステップ 2 · 商品タップ → 自社の詳細ページへ
func handleProductItem(shortsId: String, shortsSrn: String, product: ProductData) {
// product.url · sku · productId · customerProductId のうちアプリが使うキーで遷移
guard let urlStr = product.url, let url = URL(string: urlStr) else { return }
navigationController?.pushViewController(ProductDetailViewController(url: url), animated: true)
}
func handleProductBanner(shortsId: String, shortsSrn: String, scheme: String) {
// バナーはコンソールで指定したスキーム/URL がそのまま渡されます
openDeepLink(scheme)
}
override fun getOnClickProductListener() = ShopLiveShortformProductListener { context, data, product ->
// product.url · sku · productId のうちアプリが使うキーで遷移
product.url?.takeIf { it.startsWith("http") }?.let { url ->
ProductDetailActivity.start(context, url)
}
}
override fun getOnClickBannerListener() = ShopLiveShortformUrlListener { context, data, url ->
openDeepLink(context, url)
}
customerProductId を使ってください
productId は Shoplive 内部の識別子です。自社の商品詳細へ送るなら sku か url で遷移するほうが安全です。iOS ではコンソールに登録した自社商品 ID(
customerProductId)もコールバックで受け取れますが、Android の商品コールバックモデルにこのフィールドはありません。ステップ 3 · シェアタップ → 自社のシェアシート
シェアボタンが押されると、SDK はアプリにコールバックを渡します。コンソールや Web 側が共有 URL を返す場合は
handleShare(shareUrl:)、そうでない場合は handleShare(shareMetadata:) が呼ばれます。
アプリが自前のディープリンク/ダイナミックリンクを作ってシェアシートを出します。
handleShare(shareUrl:) を実装しないと SDK が既定のシェアシートを表示します
shareMetadata 側だけ実装して shareUrl 側を空にしておくと、URL 経路のシェアは
SDK が iOS 既定のシェアシートを代わりに表示します。自社のシェアシートだけを使うなら両方のオーバーロードを実装してください。func handleShare(shareMetadata: ShopLiveShareMetaData) {
// title · descriptions · thumbnail · shortsId · shortsSrn
let link = makeMyDeepLink(shortsId: shareMetadata.shortsId)
let vc = UIActivityViewController(activityItems: [shareMetadata.title ?? "", link],
applicationActivities: nil)
// ショートは別ウィンドウ上に出るため、そのウィンドウを基準に present します
ShopLiveShortform.getCurrentKeyWindow()?.rootViewController?.present(vc, animated: true)
}
override fun onShare(context: Context, data: ShopLiveShortformShareData) {
// shortsId · srn · title · description · thumbnail
val link = makeMyDeepLink(data.shortsId)
val send = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, "${data.title}\n$link")
}
context.startActivity(Intent.createChooser(send, null))
}
present すると、シェアシートがショートの背面に隠れて見えません。必ず
ShopLiveShortform.getCurrentKeyWindow() が返すウィンドウを基準に表示してください。ステップ 4 · 一般イベントを受け取る
func onEvent(messenger: ShopLiveShortformMessenger?, command: String, payload: String?) {
switch command {
case "VIDEO_MUTED": isMuted = true
case "VIDEO_UNMUTED": isMuted = false
case "DETAIL_ON_PLAYER_SHOWN": analytics.log("shorts_detail_open")
case "COLLECTION_CLICK_ITEM": analytics.log("shorts_card_click")
default: break // 知らないコマンドは無視
}
}
override fun onEvent(
context: Context,
messenger: ShopLiveShortformMessageListener?,
command: String,
payload: Map<String, Any?>
) {
when (command) {
"VIDEO_MUTED" -> isMuted = true
"VIDEO_UNMUTED" -> isMuted = false
else -> Unit // 知らないコマンドは無視
}
}
default を必ず用意してください
イベントコマンドの一覧は SDK の更新で追加されることがあります。知っている値だけを処理し、
残りは無視するように書いておけば、SDK を上げてもアプリが壊れません。一覧は型 & ステータスを参照してください。完了確認
- 商品カードをタップすると自社アプリの商品詳細へ遷移する
- シェアをタップすると自社リンク入りのシェアシートが出る(ショート画面に隠れない)
- ミュート切り替え時に
VIDEO_MUTED/VIDEO_UNMUTEDがログに出る - エラー時に
onErrorのcode/messageを捕捉できる(エラー処理)
前へ ← ミッション 4次へ → ミッション 6 · 自前のリストで再生する
ミッション 6 · 自前のリストで再生する
ミッション 1 の検索条件(タグ・ブランド)はShoplive がリストを決める方式です。 レコメンドのように自社サーバーが順序を決める必要があるなら、ショート ID の配列を直接渡します。
- 自社 API で最初のページのショート ID を取得します。
- ID リストと開始位置(
currentId)を渡して再生します。 - SDK が末尾に達すると次ページを要求します — アプリが取得して渡します。
// 1・2) 最初のページを再生
let ids = firstPage.map { ShopLiveShortformIdData(shortsId: $0.id, payload: nil) }
ShopLiveShortform.play(
shortformIdsData: ShopLiveShortformIdsData(ids: ids, currentId: ids.first?.shortsId),
dataSourceDelegate: self,
shortsCollectionDelegate: self
)
// 3) ページネーション — 下方向・上方向それぞれ
extension MyFeed: ShortsCollectionViewDataSourcRequestDelegate {
func onShortformListDownwardPagination(
completion: @escaping ((ShopLiveShortformIdsMoreData?, Error?)) -> ()
) {
myAPI.next { page, error in
guard let page = page else { completion((nil, error)); return }
let more = ShopLiveShortformIdsMoreData(
ids: page.items.map { ShopLiveShortformIdData(shortsId: $0.id, payload: nil) },
hasMore: page.hasMore
)
completion((more, nil))
}
}
func onShortformListUpwardPagingation(
completion: @escaping ((ShopLiveShortformIdsMoreData?, Error?)) -> ()
) {
completion((nil, nil)) // 上方向に追加がなければ nil
}
}
// 1・2・3) 再生とページネーション(コルーチン版)
val idsData = ShopLiveShortformIdsData().apply {
ids = firstPage.map { ShopLiveShortformIdData(it.id) }
currentId = firstPage.firstOrNull()?.id
handler = myHandler
}
ShopLiveShortform.play(
context,
idsData,
moreSuspendListener = { // 下方向に追加取得
val page = myApi.next()
ShopLiveShortformIdsMoreData().apply {
ids = page.items.map { ShopLiveShortformIdData(it.id) }
hasMore = page.hasMore
}
},
previousSuspendListener = { null } // 上方向に追加がなければ null
)
// コルーチンを使わない場合は Future 版もあります
// ShopLiveShortform.play(context, idsData, moreFutureListener, previousFutureListener)
hasMore = false を必ず返してください
最終ページで hasMore を false にしないと、SDK が次ページを要求し続けます。
空配列 + hasMore=false で終端を知らせてください。ShopLiveShortformIdData.payload にレコメンド理由やランキングなどのアプリ側データを載せておくと、
イベントコールバックでそのまま受け取ってログに使えます。完了確認
- 自社サーバーが返した順序どおりに再生される
currentIdで指定したショートが最初の画面になる- 末尾までスクロールすると次ページが継ぎ足される
- 最終ページ以降、追加リクエストが止まる(
hasMore=false)
前へ ← ミッション 5次へ → ミッション 7 · UI カスタマイズ
ミッション 7 · UI カスタマイズ
方法 ① 詳細画面のボタンを隠す
いいね・コメント・ブックマーク・シェアは個別にオフにできます。自社サービスにない機能は隠したほうが混乱がありません。
ShopLiveShortform.setVisibileDetailViews(options: ShopLiveShortformVisibleDetailData(
isBookMarkVisible: false,
isShareButtonVisible: true,
isCommentButtonVisible: false,
isLikeButtonVisible: true
))
ShopLiveShortform.setVisibleDetailTypeViews(
ShopLiveShortformVisibleDetailTypeData(
isBookmarkVisible = false,
isShareButtonVisible = true,
isCommentButtonVisible = false,
isLikeButtonVisible = true
)
)
play・showPreview を呼ぶ前に指定すると、
その画面から反映されます。方法 ② 映像の表示方式
ShopLiveShortform.setResizeMode(mode: .CENTER_CROP) // .FIT | .AUTO
ShopLiveShortform.setResizeMode(ShopLiveResizeMode.CENTER_CROP) // FIT | AUTO
| 値 | 動作 | 使いどころ |
|---|---|---|
CENTER_CROP | 画面いっぱいに広げ、はみ出す部分を切り取る | 縦型動画のみのサービス |
FIT | 映像全体が見えるように合わせる(余白が出る) | 横長・正方形が混在するサービス |
AUTO | 映像の比率に応じて SDK が選択 | 既定値 · 比率が混在するとき |
方法 ③ コレクションカードの表示項目
builder.setVisibleTitle(isVisisble: true)
builder.setVisibleDescription(isVisible: false)
builder.setVisibleBrand(isVisible: true)
builder.setVisibleProductCount(isVisible: true)
builder.setVisibleViewCount(isVisible: false)
builder.setCellSpacing(spacing: 8)
builder.setCellCornerRadius(radius: 16)
builder.setCellBackgroundColor(color: .white)
builder.setCardViewType(type: .type2)
builder.reloadItems() // 変更を反映
with(binding.shortsCardTypeView) {
setVisibleTitle(true)
setVisibleDescription(false)
setVisibleBrand(true)
setVisibleProductCount(true)
setVisibleViewCount(false)
setRadius(16f.toPx(context))
setCardBackgroundColor(Color.WHITE)
setViewType(ShopLiveShortform.CardViewType.CARD_TYPE2)
submit() // 変更を反映
}
方法 ④ プラットフォーム固有のオプション
| オプション | プラットフォーム | 説明 |
|---|---|---|
setSystemBarColor(data) | Android | 詳細画面のステータスバー・ナビゲーションバーの色をアプリのテーマに合わせる |
setSoftInputMode(mode) | Android | コメント入力時のキーボード挙動(既定 ADJUST_RESIZE) |
setMaxCount(n) | Android(コレクション) | コレクションに表示する最大件数を制限 |
setEnableResumeOnForeGround(enable:) | iOS | アプリがフォアグラウンドに戻ったとき再生を再開するか |
avAudioSessionCategoryOptions | iOS(ビルダー) | 他アプリの音声とのミキシング方針 |
setPlayOnlyWifi(true) | 共通 | Wi-Fi のときだけコレクションを自動再生 — 通信量に敏感なユーザーへの配慮 |
完了確認
- オフにしたボタンが詳細画面に表示されない
- 映像が意図した方式(いっぱいに表示/全体表示)になる
- カードからオフにした項目が消え、オンの項目だけ残る
- Android でステータスバーの色がアプリのテーマと食い違わない
追加機能
ミッションに含まれない、必要なときだけ使う機能です。
コレクションデータだけを取得する(Android)
画面を出さずにデータだけ取得してアプリ側で描画したり、横型カルーセルを複数まとめて埋めたいときに使います。
ShopLiveShortform.get(context, collectionData, object : ShopLiveShortformCollectionListener {
override fun onData(data: ShopLiveShortformCollectionResponse) {
binding.shortsHorizontalTypeView.submit(collectionData, data)
}
override fun onError(e: Exception) { /* … */ }
})
// iOS には同等の公開 API がありません。
// 一覧だけが必要な場合は、自社サーバーから Shoplive Open API で取得し、
// ミッション 6(自前のリストで再生)の ID 方式で渡す構成を推奨します。
get() には LifecycleOwner が必要です
内部で lifecycleScope を使うため、Activity/Fragment のコンテキストである必要があります。
そうでないと何も起きないまま静かに終わります。applicationContext を渡さないでください。動画キャッシュの制御(iOS)
ショートは再生した動画をディスクにキャッシュします。長く使われるアプリでは容量が積み上がるため、 設定画面に「キャッシュを削除」のような導線があれば併せて消してください。
// いま溜まっているキャッシュを削除します
AVAssetDownloadManager.shared.deleteCaches()
// アプリ終了時に自動で削除する設定(アプリ起動時に 1 回)
AVAssetDownloadManager.shared.enableDeleteCacheOnTerminate(isEnabled: true)
// Android には同等の公開キャッシュ制御 API がありません。
// 再生エンジン(media3 / ExoPlayer)のキャッシュ方針に従います。
流入トラッキング(referrer · UTM)
どの画面からショートに入ったかを記録して、コンバージョンを分析します。
ShopLiveShortformCollectionData・ShopLiveShortformPreviewData・
ShopLiveShortformIdsData の init は referrer 引数を省略しても全体の値を nil で上書きします。
setReferrer で先に設定していても、その後データオブジェクトを作ると消えるため、
データオブジェクトを使う画面では必ず init の referrer: で渡してください。ShopLiveShortform.setReferrer(referrer: "home_banner") // 全体
// またはデータ生成時: ShopLiveShortformCollectionData(referrer: "home_banner", …)
ShopLiveCommon.setUtmSource(utmSource: "naver")
ShopLiveCommon.setUtmMedium(utmMedium: "cpc")
ShopLiveCommon.setUtmCampaign(utmCampaign: "spring_sale")
ShopLiveCommon.setUtmContent(utmContent: "banner_a")
val data = ShopLiveShortformCollectionData().apply { referrer = "home_banner" }
// 一覧ビュー: binding.shortsCardTypeView.setReferrer("home_tab")
ShopLiveCommon.setUtmSource("naver")
ShopLiveCommon.setUtmMedium("cpc")
ShopLiveCommon.setUtmCampaign("spring_sale")
ShopLiveCommon.setUtmContent("banner_a")
ハイブリッド(WebView)連携
アプリ内の WebView でショートを表示する構成です。Web ページからのショート呼び出しをネイティブ SDK が受けて処理します。
// WebView 生成直後に接続
ShopLiveShortform.BridgeInterface.connect(myWebView)
// ショートのフルスクリーンの上にアプリ画面(例: 商品詳細)を表示するとき
ShopLiveShortform.BridgeInterface.present(viewController: productDetailVC)
// 画面を離れるとき
ShopLiveShortform.BridgeInterface.disconnect()
// WebView 生成直後にブリッジを JavaScript インターフェースとして登録
val bridge = ShopLiveShortformBridgeInterface(myWebView, null)
myWebView.addJavascriptInterface(bridge, "ShopLiveBridgeInterface")
bridge.observe(activity) // ComponentActivity のライフサイクルに合わせてメッセージを受信
// 画面を離れるとき
bridge.removeObserver()
present(viewController:) は接続用ではありません(iOS)
接続は connect · disconnect で行います。present(viewController:) は渡した画面をショートのフルスクリーンの上に表示します(overFullScreen)。データフィールド辞典
検索条件 — どのショートを表示するか
以下の条件は再生・コレクション・プレビューのすべてで同じ意味です。
| フィールド | 型 | 意味 |
|---|---|---|
shortsId | String | 特定のショートを最初の画面に指定(ディープリンクで使用) |
shortsCollectionId | String | コンソールで作成したコレクション単位で取得 |
tags | String[] | ハッシュタグで絞り込み |
tagSearchOperator | AND · OR | AND=すべてのタグを満たす · OR=いずれか(型) |
brands | String[] | ブランド識別子で絞り込み |
skus | String[] | 商品 SKU が紐づくショートのみ |
productId | String | 特定商品に関連するショート(主にプレビュー) |
shuffle | Bool | 順序をシャッフルして毎回違う並びに |
isMuted | Bool | 再生開始時にミュートするか |
referrer | String | 流入地点の記録(付録) |
tags と brands を同時に渡すと、両方を満たすショートだけが返ります。
結果が 0 件なら条件を 1 つずつ外して確認してください。ShopLiveShortformCollectionData — フルスクリーン再生・コレクション取得
| 区分 | iOS | Android |
|---|---|---|
| 生成 | ShopLiveShortformCollectionData(…, delegate:) — init 引数 | ShopLiveShortformCollectionData().apply { … } — プロパティ |
| イベント受信 | delegate | handler |
| 検索条件 | 上の検索条件の表と同じ | |
ShopLiveShortformPreviewData — フローティングプレビュー
検索条件に表示オプションを加えた形です。
| オプション | iOS | Android | 既定値 |
|---|---|---|---|
| 位置 | previewPosition | setPosition(…) | bottomRight |
| サイズ | previewScale | setWidth · setHeight | iOS 画面幅から自動 · Android 長辺 180dp |
| 角丸 | previewRadius | setRadius | 10 |
| 外側の余白 | previewEdgeInset | setMargin{Top,Bottom,Left,Right} | 16 |
| 閉じるボタン | useCloseButton | setUseCloseButton | true |
| スワイプで閉じる | enableSwipeOut | setEnabledSwipeOut | true |
| 最大件数 | maxCount | maxCount | 制限なし |
| タップの横取り | useCustomAction + clickEventCallBack | setUseCustomAction + setOnClickListener | false |
| タップ時のハプティクス | — | setCanVibrated | — |
| 音量キーの利用 | isEnabledVolumeKey | isEnabledVolumeKey | false |
| 自動クローズ | — | setIsAutoClosed | — |
| 遷移アニメーション | — | setCanTransition | — |
setWidth · setHeight · setMargin… · setRadius には dp ではなく px を渡します。既定値(長辺 180 · 余白 16 · 角丸 10)は dp 基準で、端末の密度に合わせて px に変換されます。ShopLiveShortformIdsData — 自前のリストで再生
| フィールド | 型 | 意味 |
|---|---|---|
ids | ShopLiveShortformIdData[] | 再生するショート ID の一覧(並び順=再生順) |
currentId | String | 最初の画面に表示するショート ID |
isMuted | Bool | 開始時にミュート(Android) |
referrer | String | 流入地点 |
| ShopLiveShortformIdData / IdsMoreData | 意味 |
|---|---|
IdData.shortsId | ショート ID |
IdData.payload | アプリが載せる任意データ(レコメンド理由・ランキングなど) |
IdsMoreData.ids | 継ぎ足す次ページの ID 一覧 |
IdsMoreData.hasMore | false で SDK が追加要求を止めます |
コレクションのオプション
| オプション | iOS(ビルダー) | Android(ビュー) |
|---|---|---|
| カードの形 | setCardViewType(type:) | setViewType(type) |
| 列数 | — | setSpanCount(n) |
| スナップスクロール | enableSnap() / disableSnap() | enableSnap() / disableSnap() |
| 自動再生 | enablePlayVideos() / disable… | enablePlayVideos() / disable… |
| Wi-Fi のみ再生 | setPlayOnlyWifi(isEnabled:) | setPlayOnlyWifi(isEnabled) |
| セル間隔 | setCellSpacing(spacing:) | レイアウト/デコレーション |
| 角丸 | setCellCornerRadius(radius:) | setRadius(radius) |
| 背景色 | setCellBackgroundColor(color:) | setCardBackgroundColor(colorInt) |
| 表示項目 | setVisibleTitle/Description/Brand/ProductCount/ViewCount | 同じ |
| 再生対象(横型) | setPlayableType(type:) | setPlayableType(type) |
| 最大件数 | — | setMaxCount(n) |
| 先頭へ | scrollToTop() | scrollToTop(isSmooth) |
| 条件の反映 | submit() · reloadItems() | submit() |
詳細画面の表示オプション
| フィールド | 既定値 | 説明 |
|---|---|---|
isLikeButtonVisible | true | いいね — 認証が必要 |
isCommentButtonVisible | true | コメント — 認証が必要 |
isBookMarkVisible(Android: isBookmarkVisible) | true | ブックマーク — 認証が必要 |
isShareButtonVisible | true | シェア — アプリが処理 |
ユーザー情報(ShopLiveCommonUser)
| フィールド | 必須 | 説明 |
|---|---|---|
userId | ✔ | 会員識別子 — 原文ではなくハッシュ値を推奨(ミッション 4) |
userName | 表示名 | |
age | 年齢 | |
gender | iOS .male/.female/.netural · Android MALE/FEMALE/NEUTRAL | |
userScore | ランク・スコア(パーソナライズに利用) | |
custom | アプリが追加で載せる任意の値 |
ProductData — 商品コールバックで受け取る値
| フィールド | 説明 |
|---|---|
productId | Shoplive 内部の商品 ID |
customerProductId | 自社の商品 ID — iOS 専用(Android のコールバックモデルにはありません) |
sku | 在庫管理コード |
url | コンソールに登録された商品 URL |
name · brand · descriptions | 表示用の情報 |
imageUrl | 商品画像 |
currency · originalPrice · discountPrice · discountRate | 価格情報 |
showPrice | 価格を表示するか(コンソール設定) |
stockStatus | 在庫状態 |
シェア用メタデータ
| フィールド | 説明 |
|---|---|
shortsId · shortsSrn(Android srn) | ショートの識別子 — ディープリンクに載せる値 |
title · description | シェア文面に使うタイトル・説明 |
thumbnail | OG 画像に使うサムネイル URL |
型 & ステータス
カード/コレクションのタイプ
| 種類 | iOS | Android |
|---|---|---|
| カードの形 | CardViewType.type0 / type1 / type2 | CardViewType.CARD_TYPE0 / 1 / 2 |
| コレクションの方向 | ListViewType.vertical / horizontal | ビュークラスで区別(Vertical / Horizontal) |
PlayableType — コレクションで何を再生するか
| 値 | 動作 | 使いどころ |
|---|---|---|
FIRST | 最初のセルのみ再生(既定) | バッテリー・通信量の節約 |
CENTER | 画面中央のセルを再生 | 横型カルーセル |
ALL | 見えているセルをすべて再生 | インパクト優先 · 性能に注意 |
PreviewPosition
| iOS | Android |
|---|---|
.topLeft · .topRight · .bottomLeft · .bottomRight · .default |
TOP_LEFT · TOP_RIGHT · BOTTOM_LEFT · BOTTOM_RIGHT |
.default は bottomRight と同じです。ShopLiveResizeMode
| 値 | 説明 |
|---|---|
CENTER_CROP | 画面いっぱいに広げ、はみ出す部分を切り取る |
FIT | 映像全体が見えるように合わせる |
AUTO | 映像の比率から自動選択(既定) |
TagSearchOperator
| 値 | 意味 |
|---|---|
AND | 指定したタグをすべて持つショート |
OR | タグのいずれかを持つショート |
イベントコマンド
ミッション 5 の onEvent に渡される値です。この一覧は今後増える可能性があるため、
知っている値だけを処理し、残りは無視するように書いてください。
| コマンド | タイミング |
|---|---|
VIDEO_MUTED / VIDEO_UNMUTED | ミュート切り替え |
DETAIL_ON_PLAYER_SHOWN(iOS)DETAIL_ON_PLAYER_SHOW(Android) | 詳細(フルスクリーン)に入った — プラットフォームで文字列が異なります(Android は末尾の N なし) |
DETAIL_EMPTY | 取得結果 0 件 — 条件が狭すぎるか、コンソールに該当ショートがない |
DETAIL_ON_PLAYER_DISMISS | 詳細から離れた |
COLLECTION_SHOW | コレクションが表示された |
COLLECTION_CLICK_ITEM | コレクションカードがタップされた |
PREVIEW_SHOWN / PREVIEW_HIDDEN | プレビューの表示/非表示 |
PREVIEW_CLICK_SHOW | プレビューをタップして詳細に拡大 |
PREVIEW_CLICK_CLOSE | プレビューの閉じるボタンをタップ |
String?)、Android は Map(Map<String, Any?>)で渡されます。
共通ロジックを置くなら、アプリ側でどちらかの形に正規化してください。エラー処理
エラーは onError(デリゲート/ハンドラー)と、コレクション専用の onListViewError(iOS)で渡されます。
両プラットフォームとも同じコード体系です。
コードの帯域
| 帯域 | 意味 |
|---|---|
| 9000 番台 | 初期化・設定の問題 — アプリ側で直すもの |
| 9300 番台 | エンコード — 主にエディター SDK |
| 9500 番台 | 未対応のメディア・機能 |
| 9900 番台 | ネットワーク・パース |
| 10000 | 分類されていない例外 |
よく発生するエラー
| コード | 名前 | 原因・対処 |
|---|---|---|
9000 | NOT_INITIALIZED_ACCESS_KEY | setAccessKey の前に SDK を呼んだ → Application.onCreate/AppDelegate へ移動 |
9001 | NOT_INITIALIZED_SHARE_URL | コンソールに共有 URL が未設定 → Shoplive コンソールを確認 |
9002 | NOT_INITIALIZED_CONFIG | ショート設定をまだ受け取れていない → ネットワークを確認して再試行 |
9050 | REQUIRED_COMPONENT_ACTIVITY | (Android)ComponentActivity 以外のコンテキストを渡した → applicationContext ではなく Activity を渡す |
9310 | REMOVED_VIDEO | 削除・非公開のショートを開こうとした → ディープリンクの shortsId の有効性を確認 |
9501 | UNSUPPORTED_MEDIA | 端末が対応していないコーデック |
9900 | FAILED_NETWORK | ネットワーク失敗 → 再試行 UI を用意 |
9901 | FAILED_JSON_PARSING | レスポンスのパース失敗 → SDK/サーバーのバージョン不一致を疑う |
9902 | FAILED_FETCH_DATA | (Android)データ取得失敗 → 検索条件・権限を確認 |
10000 | UNEXPECTED_ERROR | 分類されていない例外 → ログを添えてお問い合わせください |
func onError(error: Error) {
guard let e = error as? ShopLiveCommonError else { return }
log("shortform error \(e.codes) \(e.message ?? "")")
// 9900 番台なら再試行 UI、9000 番台は開発中に assert を推奨
}
override fun onError(context: Context, error: ShopLiveCommonError) {
log("shortform error ${error.code} ${error.message}")
when (error.code) {
ShopLiveCommonErrorCode.FAILED_NETWORK -> showRetry()
ShopLiveCommonErrorCode.NOT_INITIALIZED_ACCESS_KEY -> check(false) { "accessKey 未設定" }
else -> Unit
}
}
onError を空にしておくと、コレクションが静かに空に見えるだけで原因が分かりません。
最低限ログに残し、開発ビルドでは目に見える形(トーストなど)で表示してください。導入企業が直接設定する項目
コードではなく Shoplive コンソール・バックエンド側で準備しないと動かないものです。ここが空だと、コードが正しくても画面が空に見えます。
| 項目 | 設定場所 | なぜ必要か |
|---|---|---|
| accessKey | Shoplive が発行 | すべての呼び出しの前提(クイックスタート (Quick Start)) |
| ショートコンテンツの登録 | Shoplive コンソール | 動画・サムネイル・タイトルがないとコレクションが 0 件 |
| タグ・ブランド・グループ | Shoplive コンソール | アプリの検索条件と文字列が正確に一致している必要あり |
| 商品の紐づけ(SKU・商品 URL) | Shoplive コンソール | 商品カードの表示と遷移の根拠 |
| 共有 URL | Shoplive コンソール | 未設定だと 9001 |
| JWT シークレット | Shoplive の担当者 → 自社バックエンド | ユーザー情報の連携。アプリに入れないこと |
| ディープリンクのドメイン | 導入企業側 | 共有リンクをアプリに戻すため |
春の新作 と登録して、アプリから 春 の 新作 で検索すると 0 件になります。
定数にまとめて両側が同じ値を使うようにしてください。トラブルシューティング & FAQ
コレクションが空です
順に確認してください。① submit() を呼んだか(ミッション 2)② 検索条件が狭すぎないか —
タグ・ブランド・SKU はAND で重なります ③ コンソールにその条件のショートが実際にあるか(設定項目)
④ onError に何が出ているか。
動画が再生されずサムネイルだけ出ます(Android)
再生エンジンモジュール(shoplive-media3 または shoplive-exoplayer)がアプリの依存関係にない可能性が高いです。
ショートフォームモジュールはエンジンを compileOnly で参照するため、アプリ側で直接宣言する必要があります(インストール)。
いいねを押しても次回入ると外れています
ユーザー識別がない状態です。setAuthToken または setUser を
ショート画面に入る前に呼んでください(ミッション 4)。
シェアシートがショート画面の背面に隠れます(iOS)
フルスクリーンのショートは SDK 専用の UIWindow 上に表示されます。
ShopLiveShortform.getCurrentKeyWindow() が返すウィンドウを基準に present してください(ミッション 5)。
別の画面に移動してもプレビューが残ります
プレビューはアプリの上に浮かぶ別レイヤーなので、画面遷移では自動的に消えません。
画面を離れるときに Android は hidePreview()、iOS は close() を呼んでください(ミッション 3)。
末尾までスクロールしても次ページを要求し続けます
最終ページで hasMore を false にして返していません(ミッション 6)。
別タブに移動してもショートの音が鳴り続けます
コレクションの自動再生を有効にしたまま画面を離れた場合です。onPause/viewDidDisappear で
disablePlayVideos() を呼んでください(ミッション 2)。
アプリ内でショートを作成・アップロードさせたい
この SDK の範囲外です。ショートフォームエディター SDK ガイドをご覧ください。
ライブ(Player)SDK と併用できますか
できます。accessKey とユーザー認証は共通モジュールで 1 回だけ設定すれば、両方の SDK が使います。
ただし 2 つの SDK のバージョン帯は揃えてください。