Shoplive Developers
한국어KOEnglishEN日本語JA

Shoplive ショートフォーム SDK 連携ガイド

コマース向けショート動画をアプリに組み込む方法を、実装したいケース(ミッション)単位でご案内します。 フルスクリーン再生・コレクションの組み込み・フローティングプレビュー・商品/シェア連携まで、必要な方式だけ選んで適用できます。
コード例は上部の iOS / Android タブで切り替えられます。

はじめに

概要 & 要件

Shoplive ショートフォーム SDK は、コマースに最適化された縦型ショート動画をアプリに組み込むための SDK です。 再生・プリロード・無限スクロール・商品オーバーレイ・いいね/コメント/ブックマークといった UI は SDK がまとめて提供し、 アプリはどのショートを表示するか(検索条件)とタップしたときにどこへ遷移させるか(ルーティング)だけを担当します。

今すぐ始める →

モジュール構成

動画編集機能は別 SDK です アプリ内でショートを作成してアップロードする(UGC)機能は、この SDK ではなくショートフォームエディター SDK が担当します — エディターガイドをご覧ください。本書は見せる側(視聴)のみを扱います。

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

実装したいことミッション
ボタンを押すとショートがフルスクリーンで開き、上下にスワイプできるミッション 1 · フルスクリーン再生
ホームや特集ページの中にショートのコレクションを置くミッション 2 · コレクションを組み込む
商品詳細に関連ショートを小さなウィンドウで表示するミッション 3 · プレビュー表示
ログイン会員としていいね・コメント・ブックマークできるようにするミッション 4 · ユーザー情報の連携
商品タップで自社の詳細ページへ/シェアを自社のシートで出すミッション 5 · イベント・商品・シェア
自社サーバーが決めた順序・リストのまま再生する(パーソナライズ)ミッション 6 · 自前のリストで再生
いいね・シェアボタンを隠す、カードの表示項目を変えるミッション 7 · UI カスタマイズ

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

形態iOSAndroid
フルスクリーン再生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)
イベント受信ShopLiveShortformReceiveHandlerDelegateShopLiveShortformHandler
プラットフォーム差を一行で iOS はビルダーでビューを作り(build(...).getView())、Android は XML に置いたビューを設定して submit() します。 検索条件(タグ・ブランド・SKU・グループ)とイベントモデルは両プラットフォームで同型なので、 一方で覚えた考え方はそのままもう一方に移せます。

要件

項目iOSAndroid
最小バージョンiOS 11.0 以上shoplive-media3(既定エンジン)利用時は minSdk 23 以上 · shoplive-exoplayer 利用時は 19 以上
言語 / ツールチェーンSwift 5Kotlin · JDK 17. compileSdk 36
SDK バージョン1.8.13(ShopliveShortformSDK)1.8.14(shoplive-short-form)
必須の同梱モジュールShopliveSDKCommon(common-ios)shoplive-common · shoplive-network · 再生エンジン
バージョンを揃えて導入してください ショートフォーム SDK と共通モジュールは同じバージョン帯で使う前提で配布されています。 片方だけ上げると実行時にシンボル不一致が起きることがあります。
はじめに

インストール

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 }
}
Android — 再生エンジンは自分で追加します shoplive-short-form は共通・ネットワーク・再生エンジンを compileOnly で参照しています。 つまりアプリモジュールで宣言しないと実行時にクラスが見つかりません。 既定は shoplive-media3、レガシー ExoPlayer を使うアプリでは shoplive-exoplayer を入れてください。

各クラスの提供元

クラスモジュール(iOS)パッケージ(Android)
ShopLiveCommon · ShopLiveCommonErrorShopliveSDKCommoncloud.shoplive.sdk.common
ShopLiveShortform・コレクション・プレビューShopLiveShortformSDKcloud.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()
図版は追加予定 SDK v3 ガイドと同様の番号付きスクリーンショットは、準備でき次第ここに入ります。 番号 ①〜⑩ は画像が入っても変わりません。

コレクション(アプリ内への組み込み)

既存画面に組み込むコレクションです。カードに何を表示するかはオプションで切り替えます(ミッション 7)。

タイプ見た目使いどころ
カード型(CardType)グリッド。列数を調整可能特集・ブランド専用エリア
縦型(Vertical)下へ続くコレクションタブ全体をショートにする
横型(Horizontal)横にスワイプするカルーセルホームフィードの帯
フルフィード(DetailType / ShortsCollectionView)詳細画面をそのままビューにタブ全体がショートのアプリ
カード内の要素既定値オフにする方法
サムネイル/自動再生再生 ONdisablePlayVideos()
タイトル表示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 に同梱されるデモアプリは、本ガイドのミッションと同じケースメニュー(ミッションマップ)で構成されています。 コードを書く前にデモで動きを見ておくと理解が早くなります。

  1. 既定モード — Shoplive デモ設定: インストール直後、入力なしですべてのケースをデモデータで確認できます。
  2. 自社情報モード — 導入企業の設定を入力: 設定画面に取得済みの accessKey と(任意で)ユーザー JWT を入力すると、 自社のショートと自社ユーザーで同じケースを体験できます。
  3. オプションダイアログでタグ・ブランド・SKU・シャッフル・カードタイプ・プレビュー位置を変えながら、 ミッション 7 のオプションが画面にどう反映されるか確認できます。
デモアプリの配布チャネル(TestFlight / APK)とデモ用 accessKey は、SDK 提供時に併せてご案内します。

前へ ← 画面用語ガイド次へ → ミッション 1 · フルスクリーンで再生する

ミッション · 表示

ミッション 1 · フルスクリーンで再生する

ゴールボタンをタップすると縦型フルスクリーンが開き、上下スワイプで送れる
事前準備インストール完了 · accessKey(取得方法)
  1. アプリ起動時に setAccessKey が呼ばれているか確認します(クイックスタート (Quick Start))。
  2. 入口ボタンのアクションで検索条件を組み立て、play を呼びます。
  3. (任意)デリゲート/ハンドラーを接続して商品・シェアのイベントを受け取ります — ミッション 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 を一緒に渡すと、そのショートが最初の画面として開き、残りが後ろに続きます。 プッシュやディープリンクで特定のショートを開くケースがこれにあたります。
画面遷移を自分で制御したいとき(Android) ShopLiveShortform.intent(context, data) が Intent を返します。 自分で startActivity を呼べば、トランジションやフラグをアプリ側で決められます。

閉じる

ShopLiveShortform.close()      // アプリ側から閉じるとき(ログアウト、ディープリンクなど)
ShopLiveShortform.close()      // アプリ側から閉じるとき(ログアウト、ディープリンクなど)

完了確認

  • タップから 1〜2 秒で動画が再生される
  • 上下スワイプで前後のショートに移動する
  • 商品が登録されたショートでは下部に商品カードが表示される(画面用語 ④)
  • 条件を誤って結果が 0 件ならデータなしの案内が出る — 条件を絞りすぎていないか確認

関連: イベント · UI オプション次へ → ミッション 2 · コレクションを組み込む

ミッション · 表示

ミッション 2 · コレクションを画面に組み込む

ゴールホームや特集画面にコレクションを置き、タップでフルスクリーンにつなげる
事前準備ミッション 1 完了(検索条件を理解している)

コレクションには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() が必要です
}
横型は先に取得したデータを渡します(Android) 横型の 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 · プレビューを表示する

ゴール商品詳細の上に関連ショートを小窓で出し、タップでフルスクリーンに拡大する
事前準備ミッション 1 完了

プレビューは商品とショートをつなぐ導線に使います。商品詳細に来たユーザーに、 その商品が登場するショートを小窓で見せ、興味を示したらフルスクリーンに上げる流れです。

  1. 商品画面に入るときに productId または skus で関連ショートを指定します。
  2. 位置・サイズ・閉じるボタンなどの表示オプションを決めます。
  3. 画面を離れるときにプレビューを閉じます。
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()) { /* … */ }
画面を離れるときは必ず閉じてください プレビューは画面ではなくアプリの上に浮いています。商品詳細を離れるときに閉じないと、 別の画面の上にそのまま残ります。Android は hidePreview()、iOS は close() です。
タップ時の動作を横取りしたいとき(iOS) useCustomAction: true と clickEventCallBack を併せて渡すと、 プレビューのタップ時にフルスクリーンへ拡大せずアプリが望む画面へ遷移させられます。

完了確認

  • 商品詳細に入ると指定した隅に小窓が出る
  • タップするとフルスクリーン詳細に拡大する
  • 閉じるボタン(またはスワイプ)で消える
  • 別の画面に移動してもプレビューが残らない

関連: 商品タップイベント次へ → ミッション 4 · ユーザー情報の連携

ミッション · 連携

ミッション 4 · ユーザー情報を連携する

ゴールログイン会員としていいね・コメント・ブックマークを残し、次回も保持されるようにする
事前準備accessKey 設定済み ·(セキュア認証の場合)バックエンドでの JWT 発行準備(ステップ 4)
関連リファレンス ShopLiveCommonUser認証エラー
setAuthToken・setUser はユーザー情報を渡すもので、ユーザーを認証するものではありません ログイン(ID・パスワード、ソーシャルログインなど)はすべてアプリ側で行います。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)です
推奨の流れ アプリ起動(未ログイン)→ 何もしない → ログイン中:ショート画面に入る直前ごとにバックエンドから新しく発行した 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 の平文を露出させない

会員 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) }
}
セキュリティに関する注意 ハッシュ化は平文の隠蔽にすぎず、認証ではありません。権限の検証が必要な場合は必ずサーバー署名の JWT(setAuthToken)を使用してください。salt のポリシーは Shoplive チームと合意した値を推奨します。

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

setAuthToken に渡す JWT は、Shoplive が発行した Secret Key で顧客企業のバックエンドが署名します。アプリはバックエンドから JWT を受け取り、setAuthToken に渡すだけです。 ライブ(Player)SDK と同じ JWT です。

  1. Shoplive の担当者から Secret Key を受け取ります — base64 でエンコードされた文字列です
  2. ログインしたユーザー情報で下記の payload を作り、HS256 で署名します
  3. アプリから要求されるたびに(ショート画面に入る直前ごとに)JWT を新しく発行して返します。アプリに JWT を保存して使い回さないでください
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();
Secret Key はバックエンドだけに アプリのコードやバンドルに入れると、誰でも取り出して他のユーザーになりすましたトークンを作れます。JWT はバックエンドでのみ生成し、アプリは受け取ったトークンを setAuthToken に渡すだけにします。プロフィール(setUser)は署名がないため、権限の検証には使えません。

ステップ 5 · 呼び出すタイミング

  • アプリのログイン直後 — ショート画面に入る前に設定
  • ショート画面に入るたびに — バックエンドで新しく発行した JWT で再度 setAuthToken(保存したトークンを使い回さない)
  • ログアウト時 — ユーザー情報をリセットして前のユーザーの記録が混ざらないようにする

完了確認

  • ログイン後、いいねを押してアプリを再起動しても状態が保持される
  • ログイン後、コメント投稿がブロックされない
  • ログアウト → ユーザー情報をリセットしてから入り直すと、いいね・ブックマークが初期化される(ゲストとして視聴される)
ライブ SDK と同じユーザー情報を使います ライブ(Player)SDK をすでに導入していれば、同じ JWT・同じユーザーをそのまま使います。 両方を使うアプリでは、認証設定を1 か所で 1 回だけ行えば済みます。

前へ ← ミッション 3次へ → ミッション 5 · イベント・商品・シェア

ミッション · 連携

ミッション 5 · イベント・商品・シェア

ゴール商品タップで自社の詳細ページへ、シェアタップで自社のシェアシートを表示する
事前準備ミッション 1 または ミッション 2 完了

SDK は画面を描くだけで、判断はアプリに委ねます。商品をタップしたときどの画面へ送るか、 シェア時にどんなリンクを作るかはすべてアプリが決めます。その経路がデリゲート(iOS)/ハンドラー(Android)です。

デリゲートの構成 — 大きく 3 種類

対象iOSAndroid
商品タップ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:) が呼ばれます。 アプリが自前のディープリンク/ダイナミックリンクを作ってシェアシートを出します。

iOS — 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))
}
iOS — present する対象ウィンドウに注意 フルスクリーンのショートは SDK 専用の UIWindow 上に表示されます。アプリのルートビューコントローラーから 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 · 自前のリストで再生する

ゴール自社サーバーが決めたショート ID と順序のまま再生し、末尾で次ページを継ぎ足す
事前準備ミッション 1 完了 · ショート ID を返す自社 API

ミッション 1 の検索条件(タグ・ブランド)はShoplive がリストを決める方式です。 レコメンドのように自社サーバーが順序を決める必要があるなら、ショート ID の配列を直接渡します。

  1. 自社 API で最初のページのショート ID を取得します。
  2. ID リストと開始位置(currentId)を渡して再生します。
  3. 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 で終端を知らせてください。
payload で自社データを一緒に運べます 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アプリがフォアグラウンドに戻ったとき再生を再開するか
avAudioSessionCategoryOptionsiOS(ビルダー)他アプリの音声とのミキシング方針
setPlayOnlyWifi(true)共通Wi-Fi のときだけコレクションを自動再生 — 通信量に敏感なユーザーへの配慮

完了確認

  • オフにしたボタンが詳細画面に表示されない
  • 映像が意図した方式(いっぱいに表示/全体表示)になる
  • カードからオフにした項目が消え、オンの項目だけ残る
  • Android でステータスバーの色がアプリのテーマと食い違わない

前へ ← ミッション 6次へ → 付録 · 追加機能

付録

追加機能

ミッションに含まれない、必要なときだけ使う機能です。

コレクションデータだけを取得する(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)

どの画面からショートに入ったかを記録して、コンバージョンを分析します。

iOS — データオブジェクトを作ると全体の referrer が消えます 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)。
リファレンス

データフィールド辞典

検索条件 — どのショートを表示するか

以下の条件は再生・コレクション・プレビューのすべてで同じ意味です。

フィールド型意味
shortsIdString特定のショートを最初の画面に指定(ディープリンクで使用)
shortsCollectionIdStringコンソールで作成したコレクション単位で取得
tagsString[]ハッシュタグで絞り込み
tagSearchOperatorAND · ORAND=すべてのタグを満たす · OR=いずれか(型)
brandsString[]ブランド識別子で絞り込み
skusString[]商品 SKU が紐づくショートのみ
productIdString特定商品に関連するショート(主にプレビュー)
shuffleBool順序をシャッフルして毎回違う並びに
isMutedBool再生開始時にミュートするか
referrerString流入地点の記録(付録)
条件は AND で重なります tags と brands を同時に渡すと、両方を満たすショートだけが返ります。 結果が 0 件なら条件を 1 つずつ外して確認してください。

ShopLiveShortformCollectionData — フルスクリーン再生・コレクション取得

区分iOSAndroid
生成ShopLiveShortformCollectionData(…, delegate:) — init 引数ShopLiveShortformCollectionData().apply { … } — プロパティ
イベント受信delegatehandler
検索条件上の検索条件の表と同じ

ShopLiveShortformPreviewData — フローティングプレビュー

検索条件に表示オプションを加えた形です。

オプションiOSAndroid既定値
位置previewPositionsetPosition(…)bottomRight
サイズpreviewScalesetWidth · setHeightiOS 画面幅から自動 · Android 長辺 180dp
角丸previewRadiussetRadius10
外側の余白previewEdgeInsetsetMargin{Top,Bottom,Left,Right}16
閉じるボタンuseCloseButtonsetUseCloseButtontrue
スワイプで閉じるenableSwipeOutsetEnabledSwipeOuttrue
最大件数maxCountmaxCount制限なし
タップの横取りuseCustomAction + clickEventCallBacksetUseCustomAction + setOnClickListenerfalse
タップ時のハプティクス—setCanVibrated—
音量キーの利用isEnabledVolumeKeyisEnabledVolumeKeyfalse
自動クローズ—setIsAutoClosed—
遷移アニメーション—setCanTransition—
Android のサイズ・余白・角丸の値は px です setWidth · setHeight · setMargin… · setRadius には dp ではなく px を渡します。既定値(長辺 180 · 余白 16 · 角丸 10)は dp 基準で、端末の密度に合わせて px に変換されます。

ShopLiveShortformIdsData — 自前のリストで再生

フィールド型意味
idsShopLiveShortformIdData[]再生するショート ID の一覧(並び順=再生順)
currentIdString最初の画面に表示するショート ID
isMutedBool開始時にミュート(Android)
referrerString流入地点
ShopLiveShortformIdData / IdsMoreData意味
IdData.shortsIdショート ID
IdData.payloadアプリが載せる任意データ(レコメンド理由・ランキングなど)
IdsMoreData.ids継ぎ足す次ページの ID 一覧
IdsMoreData.hasMorefalse で 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()

詳細画面の表示オプション

フィールド既定値説明
isLikeButtonVisibletrueいいね — 認証が必要
isCommentButtonVisibletrueコメント — 認証が必要
isBookMarkVisible(Android: isBookmarkVisible)trueブックマーク — 認証が必要
isShareButtonVisibletrueシェア — アプリが処理

ユーザー情報(ShopLiveCommonUser)

フィールド必須説明
userId✔会員識別子 — 原文ではなくハッシュ値を推奨(ミッション 4)
userName表示名
age年齢
genderiOS .male/.female/.netural · Android MALE/FEMALE/NEUTRAL
userScoreランク・スコア(パーソナライズに利用)
customアプリが追加で載せる任意の値

ProductData — 商品コールバックで受け取る値

フィールド説明
productIdShoplive 内部の商品 ID
customerProductId自社の商品 ID — iOS 専用(Android のコールバックモデルにはありません)
sku在庫管理コード
urlコンソールに登録された商品 URL
name · brand · descriptions表示用の情報
imageUrl商品画像
currency · originalPrice · discountPrice · discountRate価格情報
showPrice価格を表示するか(コンソール設定)
stockStatus在庫状態

シェア用メタデータ

フィールド説明
shortsId · shortsSrn(Android srn)ショートの識別子 — ディープリンクに載せる値
title · descriptionシェア文面に使うタイトル・説明
thumbnailOG 画像に使うサムネイル URL
リファレンス

型 & ステータス

カード/コレクションのタイプ

種類iOSAndroid
カードの形CardViewType.type0 / type1 / type2CardViewType.CARD_TYPE0 / 1 / 2
コレクションの方向ListViewType.vertical / horizontalビュークラスで区別(Vertical / Horizontal)

PlayableType — コレクションで何を再生するか

値動作使いどころ
FIRST最初のセルのみ再生(既定)バッテリー・通信量の節約
CENTER画面中央のセルを再生横型カルーセル
ALL見えているセルをすべて再生インパクト優先 · 性能に注意

PreviewPosition

iOSAndroid
.topLeft · .topRight · .bottomLeft · .bottomRight · .default TOP_LEFT · TOP_RIGHT · BOTTOM_LEFT · BOTTOM_RIGHT
iOS の .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プレビューの閉じるボタンをタップ
payload の形がプラットフォームで異なります iOS は JSON 文字列(String?)、Android は Map(Map<String, Any?>)で渡されます。 共通ロジックを置くなら、アプリ側でどちらかの形に正規化してください。
リファレンス

エラー処理

エラーは onError(デリゲート/ハンドラー)と、コレクション専用の onListViewError(iOS)で渡されます。 両プラットフォームとも同じコード体系です。

コードの帯域

帯域意味
9000 番台初期化・設定の問題 — アプリ側で直すもの
9300 番台エンコード — 主にエディター SDK
9500 番台未対応のメディア・機能
9900 番台ネットワーク・パース
10000分類されていない例外

よく発生するエラー

コード名前原因・対処
9000NOT_INITIALIZED_ACCESS_KEYsetAccessKey の前に SDK を呼んだ → Application.onCreate/AppDelegate へ移動
9001NOT_INITIALIZED_SHARE_URLコンソールに共有 URL が未設定 → Shoplive コンソールを確認
9002NOT_INITIALIZED_CONFIGショート設定をまだ受け取れていない → ネットワークを確認して再試行
9050REQUIRED_COMPONENT_ACTIVITY(Android)ComponentActivity 以外のコンテキストを渡した → applicationContext ではなく Activity を渡す
9310REMOVED_VIDEO削除・非公開のショートを開こうとした → ディープリンクの shortsId の有効性を確認
9501UNSUPPORTED_MEDIA端末が対応していないコーデック
9900FAILED_NETWORKネットワーク失敗 → 再試行 UI を用意
9901FAILED_JSON_PARSINGレスポンスのパース失敗 → SDK/サーバーのバージョン不一致を疑う
9902FAILED_FETCH_DATA(Android)データ取得失敗 → 検索条件・権限を確認
10000UNEXPECTED_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 コンソール・バックエンド側で準備しないと動かないものです。ここが空だと、コードが正しくても画面が空に見えます。

項目設定場所なぜ必要か
accessKeyShoplive が発行すべての呼び出しの前提(クイックスタート (Quick Start))
ショートコンテンツの登録Shoplive コンソール動画・サムネイル・タイトルがないとコレクションが 0 件
タグ・ブランド・グループShoplive コンソールアプリの検索条件と文字列が正確に一致している必要あり
商品の紐づけ(SKU・商品 URL)Shoplive コンソール商品カードの表示と遷移の根拠
共有 URLShoplive コンソール未設定だと 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 のバージョン帯は揃えてください。

Shoplive ショートフォーム SDK 連携ガイド · iOS 1.8.13 / Android 1.8.14 基準 · ご利用ガイド