Shoplive Developers
한국어KOEnglishEN日本語JA

Shoplive ショートフォームエディター SDK 連携ガイド

ユーザーがアプリ内でショート動画を作成してアップロードできるようにする SDK です。 動画の選択 → 編集(トリム・クロップ・フィルター・速度・音量)→ カバー選択 → アップロードまで、画面全体を SDK が提供します。
コード例は上部の iOS / Android タブで切り替えられます。

はじめに

概要 & 要件

ショートフォームエディター SDK は UGC(ユーザー制作ショート)のための SDK です。 トリミング・クロップ・フィルター・エンコード・アップロードなど、自前で作ると時間のかかる部分を SDK がまとめて担当し、 アプリはいつ開くかと終わった後に何をするかだけを決めます。

今すぐ始める →

2 つの使い方

見せる側は別 SDK です 作成・アップロードしたショートをアプリで再生・一覧表示するのは ショートフォーム SDK ガイドが扱います。本書は作る側のみを扱います。

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

実装したいことミッション
「ショートを投稿」ボタンでエディターが開き、アップロードまで完了するミッション 1 · エディターを開く
動画の長さ・比率を自社ポリシーに合わせ、使わない編集ツールを隠すミッション 2 · 画面のカスタマイズ
ギャラリーは自社のものを使い、編集機能だけ借りる付録 · 動画エディターだけを使う
サムネイル(カバー)だけ選び直させる付録 · カバー選択

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

形態iOSAndroid
フルフローShopLiveShortformEditor.shared.start(vc)ShopLiveShortformEditor(activity).start()
エディター単体ShopliveVideoEditor.shared.build(data:completion:)ShopLiveVideoEditor(activity).start(data)
カバー選択ShopLiveCoverPicker.shared.build(data:completion:)ShopLiveCoverPicker(activity).start(data)
画像 → 動画—ShopLiveImageEditor(activity).start(data)
結果の受信ShopLiveShortformEditorDelegateShopLiveShortformEditorHandler
ショートフォーム SDK よりプラットフォーム差が大きい点に注意 iOS はエディター画面(ViewController)を返しアプリが present する形、 Android は SDK が Activity を開き結果をハンドラーで返す形です。 画像編集は Android のみ単体コンポーネントとして公開されています(カバー選択は両プラットフォームにあります)。 両プラットフォームを同時に導入する場合は、この差をアプリ層で吸収してください。

要件

項目iOSAndroid
最小バージョンiOS 13.0 以上shoplive-media3(既定エンジン)利用時は minSdk 23 以上 · shoplive-exoplayer 利用時は 19 以上
SDK バージョン1.8.12(ShopLiveShortformEditorSDK)1.8.14(shoplive-video-editor)
必須の同梱モジュールShopliveFilterSDK · ShopliveSDKCommonshoplive-common · shoplive-network
実行環境UIViewControllerComponentActivity(AppCompatActivity を含む)
アプリサイズFFmpeg ベースのエンコードを含むため、アプリ容量が目に見えて増えます
容量と ABI を事前に確認してください エディターはネイティブのエンコードライブラリを含みます。Android では armeabi-v7a・arm64-v8a・x86・x86_64 の 4 つの ABI の .so が入るため、容量が重要なら ABI 分割(App Bundle)を有効にしてください。
はじめに

インストール

// 1) Swift Package Manager
dependencies: [
    .package(url: "https://github.com/shoplive/shortform-editor-ios", .upToNextMajor(from: "1.8.12")),
    .package(url: "https://github.com/shoplive/common-ios",           .upToNextMajor(from: "1.8.12")),
]
// ターゲットに ShopLiveShortformEditorSDK と ShopliveFilterSDK を併せて追加します。

// 2) CocoaPods — Podfile
// pod 'ShopLiveShortformEditorSDK', '1.8.12'   # ShopliveFilterSDK を含む
// pod 'ShopliveSDKCommon',          '1.8.12'

// 3) import
import ShopliveSDKCommon
import ShopLiveShortformEditorSDK
dependencies {
    implementation("cloud.shoplive:shoplive-video-editor:1.8.14")   // エディター
    implementation("cloud.shoplive:shoplive-common:1.8.14")         // 共通(accessKey・認証)
    implementation("cloud.shoplive:shoplive-network:1.8.14")        // ネットワーク
    implementation("cloud.shoplive:shoplive-filter: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 }

    // ネイティブのエンコードライブラリを含む → 容量が重要なら ABI 分割を推奨
    bundle { abi { enableSplit = true } }
}

Info.plist と権限

<!-- Info.plist — 未設定だとアプリがクラッシュします -->
<key>NSPhotoLibraryUsageDescription</key>
<string>ショートにする動画を選ぶために写真へのアクセスが必要です。</string>
<key>NSCameraUsageDescription</key>
<string>ショートを撮影するためにカメラへのアクセスが必要です。</string>
<key>NSMicrophoneUsageDescription</key>
<string>動画の音声を録音するためにマイクへのアクセスが必要です。</string>
// ギャラリー選択は PickVisualMedia(フォトピッカー)を使うため、ストレージ権限は不要です。
// 撮影機能を併せて組み込む場合のみ AndroidManifest.xml に追加:
// <uses-permission android:name="android.permission.CAMERA" />
// <uses-permission android:name="android.permission.RECORD_AUDIO" />
権限拒否時の導線をアプリ側で扱えます(iOS) setPermissionHandler に ShopLivePermissionHandler を渡すと、 写真・カメラ・マイクの権限結果をアプリが受け取り、設定アプリへ誘導する案内を自前で表示できます(付録)。
はじめに

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

先に 2 つ準備してください accessKey の設定とユーザー識別の両方が必要です。 アップロードされたショートは作成者に帰属するため、ログインユーザーでないと自分の投稿を再び見つけられません。
// 1) アプリ起動時に 1 回
ShopLiveCommon.setAccessKey(accessKey: "取得した_ACCESS_KEY")
ShopLiveCommon.setAuthToken(authToken: myJwtFromServer)

// 2)「ショートを投稿」ボタンから — 選択・編集・カバー・アップロードまで一気に
ShopLiveShortformEditor.shared
    .setDelegate(delegate: self)
    .start(self)
// 1) アプリ起動時に 1 回(Application.onCreate)
ShopLiveCommon.setAccessKey("取得した_ACCESS_KEY")
ShopLiveCommon.setAuthToken(myJwtFromServer)

// 2)「ショートを投稿」ボタンから — 選択・編集・カバー・アップロードまで一気に
ShopLiveShortformEditor(this)          // this = ComponentActivity
    .setHandler(myHandler)
    .start()
Android — ComponentActivity でないと動作しません エディターは activityResultRegistry で画面結果をやり取りします。 Application/Service のコンテキストや旧式の Activity を渡すと 9050 REQUIRED_COMPONENT_ACTIVITY が出るか、何も起きません。

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

画面 & デモ

画面用語ガイド

フルフローは4 つの画面が順に続きます。以降のミッション・オプションの説明でも同じ名前を使います。

画面の流れ

段階画面ユーザーの操作アプリが制御できること
①メディア選択ギャラリーから動画を選ぶ許容する長さの範囲・拒否時の案内文(ミッション 2)
②動画編集トリム・クロップ・フィルター・速度・音量表示する編集ツール・比率・長さ制限(ミッション 2)
③カバー選択サムネイルにするフレームを選ぶ比率・クロップボタンの表示(iOS・Android)
④アップロードタイトル・説明・ハッシュタグ・商品を入力して登録入力項目の表示・初期値(タグ・商品)・公開状態(ミッション 2)
図版は追加予定 SDK v3 ガイドと同様の番号付きスクリーンショットは、準備でき次第ここに入ります。 番号 ①〜④ は画像が入っても変わりません。

編集ツール(② の画面)

ツールできることオフにする方法
トリム開始・終了位置をつまんで長さを調整常に表示(長さの範囲のみ調整可)
クロップ画面比率に合わせて切り出すiOS editOptions · Android isUsedCropButton
フィルターコンソールに登録された色補正プリセットを適用iOS editOptions · Android isUsedFilterButton
再生速度倍速を調整iOS editOptions · Android isUsedPlaybackSpeedButton
音量元音声の大きさを調整iOS editOptions · Android isUsedVolumeButton

アップロード画面(④ の画面)

入力項目既定値制御
説明(本文)表示iOS isDescriptionVisible
ハッシュタグ表示iOS isTagsVisible · Android ui.hashTag
動画の差し替え非表示Android ui.videoChange
評価(rating)非表示Android ui.rating
紐づけ商品なしAndroid products で初期値を指定
公開状態公開(OPENED)iOS shortsStatus · Android shortformStatus

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

画面 & デモ

デモアプリで確認する

デモアプリにはエディターのオプションポップアップがあり、コードを変えずにミッション 2 のオプションが 画面にどう反映されるかをすぐ確認できます。

  1. フルフロー — 選択からアップロードまで SDK が提供する既定の経路(ミッション 1)。
  2. ギャラリー + エディター — アプリのギャラリーで選んだ動画をエディターにだけ渡す経路(付録)。
  3. エディター単体 — すでに持っているファイルパス/URL でエディターだけを開く経路。
  4. オプションポップアップで比率・最小/最大の長さ・説明とタグの表示・編集ツールを変えながら確認します。
デモアプリの配布チャネル(TestFlight / APK)とデモ用 accessKey は、SDK 提供時に併せてご案内します。

前へ ← 画面用語ガイド次へ → ミッション 1 · エディターを開く

ミッション

ミッション 1 · エディターを開く

ゴール「ショートを投稿」をタップ → 選択・編集・カバー・アップロードが完了し、アプリが結果を受け取る
事前準備インストール · accessKey · ユーザー識別(クイックスタート (Quick Start))
  1. 結果を受け取るデリゲート/ハンドラーを用意します。
  2. ボタンのアクションでエディターを開始します。
  3. アップロード成功・キャンセル・エラーをそれぞれ処理します。

ステップ 1 · 開始する

@IBAction func createShorts() {
    ShopLiveShortformEditor.shared
        .setPermissionHandler(self)        // 任意 — 付録を参照
        .setDelegate(delegate: self)
        .start(self)                       // 現在の ViewController の上に表示
}

// アプリ側から強制的に閉じるとき
ShopLiveShortformEditor.shared.close()
fun createShorts() {
    ShopLiveShortformEditor(this)          // this = ComponentActivity
        .setHandler(myHandler)
        .start()
}

ステップ 2 · 結果を受け取る

extension MyViewController: ShopLiveShortformEditorDelegate {

    func onShopLiveShortformEditorUploadSuccess(result: ShopliveEditorResultData?) {
        // アップロード完了 — result.shortsId で作ったばかりのショートを再生できます
        guard let shortsId = result?.shortsId else { return }
        myFeed.insertNewShorts(shortsId)
    }

    func onShopLiveShortformEditorVideoConvertSuccess(videoPath: String) {
        // エンコード済みのローカルファイルパス(アップロード前の段階)
    }

    func onShopLiveShortformEditorCoverImageSuccess(image: UIImage?) {
        // ユーザーが選んだカバー画像
    }

    func onShopLiveShortformEditorClosed() {
        // 途中で閉じられた — 必要なら「下書き保存」を案内
    }

    func onShopLiveShortformEditorError(error: ShopLiveCommonError) {
        log("editor error \(error.codes) \(error.message ?? "")")
    }

    func onShopLiveShortformEditorOnEvent(name: String, payload: [String : Any]?) {
        analytics.log(name)                // ボタン単位の行動ログ
    }
}
val myHandler = object : ShopLiveShortformEditorHandler() {

    override fun onSuccess(activity: ComponentActivity) {
        // アップロード完了 — 一覧を更新するか「自分のショート」タブへ
        activity.finish()
        myFeed.refresh()
    }

    override fun onCancel() {
        // 途中で閉じられた
    }

    override fun onError(context: Context, error: ShopLiveCommonError) {
        log("editor error ${error.code} ${error.message}")
    }

    override fun onEvent(context: Context, command: String, payload: Map<String, Any?>) {
        analytics.log(command)             // ボタン単位の行動ログ
    }
}
成功コールバックの形がプラットフォームで異なります iOS は ShopliveEditorResultData(shortsId を含む)を返しますが、 Android のフルフローの onSuccess はActivity だけを渡します。 Android で shortsId が必要な場合はエディター単体経路の ShopLiveEditorResultData.shortsId を使うか、アップロード後に一覧を取得し直してください。

ステップ 3 · アップロード画面に初期値を入れる(Android)

商品詳細から「この商品でショートを作る」といった導線なら、商品・タグをあらかじめ埋められます。

ShopLiveShortformEditor(this)
    .setShortformUploaderData(
        ShopLiveShortformUploaderData(
            tags = listOf("春の新作"),
            products = listOf(
                ShopLiveShortformUploaderProductData(
                    customerProductId = "P-1001",
                    sku = "SKU-1001"
                )
            ),
            ui = ShopLiveShortformUploaderUiData(
                hashTag = true,
                videoChange = false,
                rating = false
            )
        )
    )
    .setHandler(myHandler)
    .start()
// iOS はアップローダーを単体コンポーネントとして開き、初期値を注入します。
// (フルフローの ShopLiveShortformEditor には注入ポイントがありません)
ShopLiveShortformUploader.shared
    .setDelegate(self)
    .setUploaderData(ShopLiveShortformUploaderData(
        ui: ShopLiveUgcUploaderUiData(hashTag: true, videoChange: false, rating: false),
        tags: ["春の新作"],
        products: [ShopLiveConversionProductData(sku: "SKU-1001", customProductId: "P-1001")]
    ))
    .build { uploaderVC in
        uploaderVC.modalPresentationStyle = .fullScreen
        self.present(uploaderVC, animated: true)
    }
let visible = ShopLiveShortFormEditorVisibleContent(
    isDescriptionVisible: true,
    isTagsVisible: true
)

完了確認

  • ボタンをタップするとメディア選択画面が開く
  • 動画を選ぶと編集画面に進む
  • アップロードを終えると成功コールバックが呼ばれる
  • 途中で閉じるとキャンセルコールバックが呼ばれ、アプリ画面が正常に戻る
  • アップロードしたショートがショートフォーム SDK の一覧で取得できる

関連: 権限の扱い次へ → ミッション 2 · 画面をカスタマイズする

ミッション

ミッション 2 · 画面をカスタマイズする

ゴール動画の長さ・比率を自社ポリシーに合わせ、使わない編集ツールと入力項目を隠す
事前準備ミッション 1 完了

方法 ① 動画の長さ・比率

let config = ShopLiveShortformEditorConfiguration(
    videoCropOption: ShopLiveShortFormEditorAspectRatio(width: 9, height: 16, isFixed: true),
    visibleContents: nil,
    videoOutputOption: ShopLiveShortformEditorVideoOuputOption(
        videoOutputQuality: .high,
        videoOutputResoltuion: ._720
    ),
    mediaPickerVideoDurationOption: ShopLiveMediaPickerVideoDurationOption(
        minVideoDuration: 3,                 // 秒
        maxVideoDuration: 60 * 5,
        invalidDurationToastMessage: "3 秒〜5 分の動画のみアップロードできます"
    ),
    videoUploadOption: nil,
    minVideoDuration: 3,                     // 編集結果の長さ(秒)
    maxVideoDuration: 60
)

ShopLiveShortformEditor.shared
    .setConfiguration(config)
    .setDelegate(delegate: self)
    .start(self)
ShopLiveShortformEditor(this)
    .setVideoEditorData(
        ShopLiveVideoEditorData(
            minVideoDuration = 3_000,        // ミリ秒
            maxVideoDuration = 60_000,
            aspectRatio = ShopLiveShortformEditorAspectRatio(
                width = 9, height = 16, isFixed = true
            ),
            outputVideoQuality = ShopLiveVideoEditorVideoQuality.HIGH,
            outputResolution = ShopLiveVideoEditorResolution.RESOLUTION_720
        )
    )
    .setHandler(myHandler)
    .start()
単位が違います — iOS は秒、Android はミリ秒 同じ「60 秒」を iOS は 60、Android は 60_000 と書きます。 共通の定数を置くなら片方の単位で定義して変換してください。1000 倍ずれると 「動画が必ず短すぎる/長すぎる」という症状として現れます。
iOS 1.8.12 — mediaPickerVideoDurationOption は反映されません ShopLiveShortformEditorConfiguration の init が引数を受け取るだけで内部に代入しないため、 メディア選択の長さ制限は常に既定値(3 秒〜15 分)で動作します。選択段階を制限する必要があれば ShopLiveMediaPicker の単体経路を使うか、SDK の修正を依頼してください。以下の説明は意図された設計で、 編集結果の長さ(min/maxVideoDuration)は正しく反映されます。
選択段階と編集段階の長さ制限は別物です(iOS) mediaPickerVideoDurationOption はギャラリーで選べる元動画の長さ、 min/maxVideoDuration は編集結果の長さです。 5 分の元動画を選ばせて 60 秒に切らせたい場合は、2 つの値を別々に指定してください。
isFixed の意味 true ならユーザーは比率を変更できません(9:16 固定)。フィードが縦型専用なら true を推奨します。 既定値は iOS が true、Android が false とプラットフォームで異なるため、両方で明示してください。

方法 ② 編集ツールを隠す

// フィルターとクロップだけ残し、速度と音量は隠す
let visible = ShopLiveShortFormEditorVisibleContent(
    isDescriptionVisible: true,
    isTagsVisible: true,
    editOptions: [.filter, .crop]        // 既定値: [.filter, .playBackSpeed, .volume, .crop]
)
ShopLiveVideoEditorData(
    visibleActionButton = ShopLiveShortformEditorVisibleActionButton(
        isUsedFilterButton = true,
        isUsedCropButton = true,
        isUsedPlaybackSpeedButton = false,
        isUsedVolumeButton = false
    )
)
フィルターはコンソールに登録されたものだけが出ます フィルターボタンを有効にしても、コンソールにプリセットがなければ一覧は空に見えます(導入企業側の設定)。

方法 ③ アップロード画面の入力項目

// 説明・ハッシュタグの表示可否 + アップロードポリシー
let visible = ShopLiveShortFormEditorVisibleContent(
    isDescriptionVisible: true,
    isTagsVisible: false                 // ハッシュタグ入力を隠す
)

let upload = ShopLiveShortFormEditorVideoUploadOption(
    shortsStatus: .CLOSED,               // アップロード直後は非公開 → 審査後に公開
    isCreatedShortform: true
)
ShopLiveShortformEditor(this)
    .setShortformUploaderData(
        ShopLiveShortformUploaderData(
            ui = ShopLiveShortformUploaderUiData(hashTag = false)
        )
    )
    .setVideoEditorData(
        ShopLiveVideoEditorData(
            shortformStatus = ShopLiveShortformStatus.CLOSED   // 非公開でアップロード
        )
    )
    .setHandler(myHandler)
    .start()
Android のフルフローは公開状態を強制します ShopLiveShortformEditor(フルフロー)は内部で isCreatedShortform = true と shortformStatus = CLOSED を上書きします。そのため以下の設定はフルフローでは無視され、常に非公開で登録されます。 公開状態を自分で制御するにはエディター単体の経路を使ってください。
UGC の審査が必要なら CLOSED でアップロードしてください CLOSED でアップロードすると、コンソールで審査してから公開できます。 ユーザー制作コンテンツを即時に表示するのが難しいサービスでは、こちらを既定にすべきです。

方法 ④ 色・アイコンを変える(Android)

Android では編集画面のボタン・スライダー・トリムの色まで差し替えられます。

ShopLiveShortformEditor(this)
    .setVideoEditorCustomize(
        ShopLiveVideoEditorCustomizeData(
            mainData = ShopLiveVideoEditorCustomizeMainData(
                titleTextSize = 16f,
                backButtonBackgroundRes = R.drawable.ic_my_back,
                closeButtonBackgroundRes = R.drawable.ic_my_close,
                confirmButtonTextColor = Color.WHITE,
                confirmButtonBackgroundRes = R.drawable.bg_my_button,
                trimColor = Color.parseColor("#245EFF"),
                trimHandleColor = Color.WHITE
            )
            // volumeData · cropData · filterData · playbackSpeedData も同じ方式
        )
    )
    .setHandler(myHandler)
    .start()
// iOS は画面ごとのグローバル config シングルトンでアイコン・色を差し替えます。
// エディターを開く前に一度だけ設定すれば十分です。
ShopLiveShortformEditor.MediaPickerConfig.global.cellCornerRadius = 8
ShopLiveShortformEditor.EditorMainConfig.global.confirmButtonBackgroundColor = .systemPink
ShopLiveShortformEditor.EditorMainConfig.global.confirmButtonTextColor = .white
ShopLiveShortformEditor.EditorCropConfig.global.cropColor = .systemPink
// このほか EditorVolumeConfig · EditorSpeedConfig · EditorFilterConfig
// · EditorCoverPickerConfig も同じように .global にアクセスします。
両プラットフォームの見た目が完全に揃うことはありません 色・アイコンはどちらでも変更できますが、方法と範囲が異なります — iOS は画面ごとの *Config.global シングルトン、 Android は setVideoEditorCustomize(…) です。ピクセル単位で揃えることを要件にせず、 機能の露出範囲を揃える方向で合意するのが現実的です。

完了確認

  • ポリシーより短い/長い動画を選ぶと案内文が出る
  • 編集画面に有効化したツールだけが表示される
  • アップロード画面に隠した入力項目が表示されない
  • アップロード結果の公開状態が意図した値(公開/非公開)になっている

前へ ← ミッション 1次へ → 付録 · 動画エディターだけを使う

付録

動画エディターだけを使う

アプリにすでにギャラリーや撮影画面があり、編集機能だけが必要な場合です。 アプリが選んだ動画を渡すとエディターだけが開き、結果をアプリが受け取ります。

// iOS — エディターの「画面」を返すので、アプリが自分で present します
ShopliveVideoEditor.shared
    .setDelegate(self)
    .setConfiguration(ShopliveVideoEditorConfiguration(
        videoCropOption: ShopliveVideoEditorAspectRatio(width: 9, height: 16, isFixed: true),
        videoOutputOption: nil,
        videoTrimOption: ShopliveVideoEditorTrimOption(minVideoDuration: 3, maxVideoDuration: 60),
        visibleContents: nil,
        videoUploadOption: nil
    ))
    .build(data: ShopLiveVideoEditorData(videoUrl: pickedFileURL, isCreatedShortform: true)) { editorVC in
        editorVC.modalPresentationStyle = .fullScreen
        self.present(editorVC, animated: true)
    }

// メモリ解放 — エディターを閉じた後に
ShopliveVideoEditor.shared.cleanUpMemory()
// Android — SDK が Activity を開き、結果をハンドラーで返します
ShopLiveVideoEditor(this)
    .setData(ShopLiveVideoEditorData(
        minVideoDuration = 3_000,
        maxVideoDuration = 60_000,
        isCreatedShortform = true
    ))
    .setHandler(object : ShopLiveVideoEditorHandler() {
        override fun onSuccess(
            videoEditorActivity: ComponentActivity,
            result: ShopLiveEditorResultData
        ) {
            // result.shortsId · localVideoUri · localCoverImageUri · duration …
            ShopLiveVideoEditor.close()
            uploadToMyServer(result.localVideoUri)
        }
        override fun onCancel() { /* ユーザーが閉じた */ }
        override fun onError(context: Context, error: ShopLiveCommonError) { log(error) }
    })
    .start(ShopLiveEditorLocalData(pickedUri))     // カメラ結果なら ShopLiveEditorCameraData

入力ソース

ソースiOSAndroid
ローカルファイルShopLiveVideoEditorData(videoUrl:)ShopLiveEditorLocalData(uri)
カメラ撮影の結果ShopLiveVideoEditorData(videoUrl:)ShopLiveEditorCameraData(uri)
リモート URLShopLiveVideoEditorData(videoRemoteUrl:)—
絶対/相対パスの組ShopLiveVideoEditorData(videoAbsoluteUrl:videoRelativeUrl:)—
isCreatedShortform の意味を確認してください true なら編集結果が Shoplive のショートとして登録されます(だから shortsId が返ります)。 編集結果のファイルだけをアプリが受け取り登録はしないなら false にしてください。
未対応のコーデックなら 9501 が返ります エディターは入力動画のコーデックを先に検査します。アプリのギャラリーから任意のファイルを渡せる構成なら、 UNSUPPORTED_MEDIA を受け取ってユーザーに案内する経路を必ず用意してください。
付録

カバー選択・画像編集

サムネイルだけを選び直させたり、静止画を短い動画にしたりする単体コンポーネントです。 カバー選択は iOS・Android 両方、画像編集は Android のみです。

カバー選択

ShopLiveCoverPicker(this)
    .setData(ShopLiveCoverPickerData(
        shortsId = "SHORTS_ID",
        aspectRatio = ShopLiveShortformEditorAspectRatio(9, 16, isFixed = true),
        visibleActionButton = ShopLiveCoverPickerVisibleActionButton(isUsedCropButton = true)
    ))
    .setHandler(object : ShopLiveCoverPickerHandler() {
        override fun onSuccess(
            coverPickerActivity: ComponentActivity,
            result: ShopLiveEditorResultData
        ) {
            ShopLiveCoverPicker.close()
            applyCover(result.localCoverImageUri)
        }
        override fun onCancel() { /* … */ }
    })
    .start(ShopLiveCoverPickerLocalData(localVideoUri))   // または ShopLiveCoverPickerUrlData(remoteUrl)
// iOS でもカバー選択を単体で開けます(画面を返すのでアプリが present します)
ShopLiveCoverPicker.shared
    .setDelegate(self)
    .setConfiguration(ShopLiveCoverPickerConfiguration(
        cropOption: ShopLiveShortFormEditorAspectRatio(width: 9, height: 16, isFixed: true),
        visibleActionButton: ShopLiveCoverPickerVisibleActionButton(editOptions: [.crop])
    ))
    .build(data: ShopLiveCoverPickerData(videoUrl: localVideoURL, shortsId: shortsId)) { pickerVC in
        self.present(pickerVC, animated: true)
    }

// 閉じた後にメモリを解放
ShopLiveCoverPicker.shared.cleanUpMemory()

画像 → 短い動画

ShopLiveImageEditor(this)
    .setData(ShopLiveImageEditorData(
        isCreatedShortform = true,
        resolution = ShopLiveVideoEditorResolution.RESOLUTION_720,
        videoDuration = 10_000                 // ミリ秒 — 画像を 10 秒の動画に
    ))
    .setHandler(object : ShopLiveImageEditorHandler() {
        override fun onSuccess(result: ShopLiveEditorResultData) { /* … */ }
        override fun onError(error: ShopLiveCommonError) { log(error) }
    })
    .start(ShopLiveEditorLocalData(pickedImageUri))
// iOS には画像 → 動画の変換 API が公開されていません。
画像 → 動画は Android 専用です 両プラットフォームで同じ機能が求められる企画なら、iOS 側はアプリが自前で実装するか、 その機能をスコープから外す判断が必要です。
付録

権限の扱い

写真・カメラ・マイクの権限が拒否されたときに、SDK の既定動作ではなくアプリが自分で案内したい場合に使います。

ShopLiveShortformEditor.shared
    .setPermissionHandler(self)
    .setDelegate(delegate: self)
    .start(self)

extension MyViewController: ShopLivePermissionHandler {
    func handlePhotoLibraryUsagePermission(status: PermissionStatus) {
        // 拒否状態なら「設定から写真へのアクセスを許可してください」という案内をアプリが表示
    }
    func handleCameraPermission(status: PermissionStatus) { /* … */ }
    func handleMicroPhonePermission(status: PermissionStatus) { /* … */ }
    func handleMediaLibraryUsagePermission(status: PermissionStatus) { /* … */ }
}
// Android はフォトピッカー(PickVisualMedia)を使うため、ストレージ権限の処理は不要です。
// 撮影を組み込んだ場合のみ、アプリが CAMERA・RECORD_AUDIO 権限を要求し、
// 許可された後に ShopLiveEditorCameraData でエディターを開始してください。
Info.plist の文言は審査対象です NSPhotoLibraryUsageDescription などの説明文が実際の用途と異なると、アプリ審査でリジェクトされることがあります。 「ショート制作のために」のように具体的に書いてください(インストール)。
リファレンス

データフィールド辞典

Configuration — 何をどこに入れるか

決めたいことiOSAndroid
画面比率videoCropOptionShopLiveVideoEditorData.aspectRatio
結果の長さminVideoDuration · maxVideoDuration(秒)minVideoDuration · maxVideoDuration(ms)
選べる元動画の長さmediaPickerVideoDurationOption—
編集ツールの表示visibleContents.editOptionsvisibleActionButton
説明・タグ入力の表示visibleContents.isDescriptionVisible/isTagsVisibleShopLiveShortformUploaderData.ui
出力品質・解像度videoOutputOptionoutputVideoQuality · outputResolution
アップロード時の公開状態videoUploadOption.shortsStatusshortformStatus
ショートとして登録するかvideoUploadOption.isCreatedShortformisCreatedShortform
色・アイコンShopLiveShortformEditor.*Config.globalsetVideoEditorCustomize(…)
アップロード初期値(商品・タグ)ShopLiveShortformUploader.setUploaderData(…)setShortformUploaderData(…)

比率と長さ

フィールド既定値(iOS)既定値(Android)説明
width : height9 : 169 : 16クロップ比率
isFixedtruefalseユーザーが比率を変更できるか — 既定値がプラットフォームで異なる
minVideoDuration1(秒)1000(ms)結果の最小の長さ
maxVideoDuration60(秒)60000(ms)結果の最大の長さ
メディア選択の最小3(秒)—ギャラリーで選べる元動画の最小の長さ
メディア選択の最大900(秒 = 15 分)—ギャラリーで選べる元動画の最大の長さ
不正な長さの組み合わせはプラットフォームごとに扱いが違います iOS は maxVideoDuration ≤ minVideoDuration のとき黙って既定値(1〜60 秒)に戻します。 Android は 1 未満の値だけを既定値(1000 ms / 60000 ms)に置き換え、max ≤ min の検証は行いません。 どちらもエラーにならないため設定が無視されたことに気づきにくい点に注意してください。値を計算して入れる場合はアプリ側で順序を検証してください。

結果データ

フィールドiOS ShopliveEditorResultDataAndroid ShopLiveEditorResultData
ショート IDshortsIdshortsId
ローカル動画localVideoUrl(String)localVideoUri(Uri)
ローカルカバーlocalCoverImage(UIImage)localCoverImageUri(Uri)
アップロード済み動画remoteOriginVideoUrlremoteOriginVideoUrl
アップロード済みカバーremoteCoverImageUrlremoteCoverImageUrl
解像度width · heightwidth · height
長さduration(Double)duration(Long)
撮影日時videoCreatedAt—

デリゲート・ハンドラー

タイミングiOS(フルフロー)Android(フルフロー)
アップロード成功onShopLiveShortformEditorUploadSuccess(result:)onSuccess(activity)
エンコード完了onShopLiveShortformEditorVideoConvertSuccess(videoPath:)—
カバー確定onShopLiveShortformEditorCoverImageSuccess(image:)—
クローズ・キャンセルonShopLiveShortformEditorClosed()onCancel()
エラーonShopLiveShortformEditorError(error:)onError(context, error)
行動イベントonShopLiveShortformEditorOnEvent(name:payload:)onEvent(context, command, payload)
タイミングiOS(エディター単体)Android(エディター単体)
成功onShopLiveVideoEditorUploadSuccess(editor:result:)onSuccess(activity, result)
エンコード完了onShopLiveVideoEditorVideoConvertSuccess(editor:videoPath:)—
キャンセルonShopLiveVideoEditorCancelled(editor:)onCancel()
エラーonShopLiveVideoEditorError(editor:error:)onError(context, error)
iOS はコールバックに editor(ViewController)が併せて渡されます エディター単体のデリゲートはどのエディターから来たイベントかを一緒に渡します。 エディターを閉じたり、その上にアラートを出したりするときにこの参照を使ってください。
リファレンス

型 & ステータス

編集ツール(iOS SLEditOptions)

値ツールAndroid の対応
.filterフィルターisUsedFilterButton
.playBackSpeed再生速度isUsedPlaybackSpeedButton
.volume音量isUsedVolumeButton
.cropクロップisUsedCropButton
iOS の既定値は 4 つすべて([.filter, .playBackSpeed, .volume, .crop])です。

出力品質・解像度

区分iOSAndroid
品質.normal · .high · .max(既定 .high)NORMAL · HIGH · MAX(既定 NORMAL)
解像度._360 · ._480 · ._720 · ._1080(既定 ._720)RESOLUTION_360/480/720/960/1080(既定 720)
既定の品質がプラットフォームで異なります 同じ動画をアップロードしても iOS のほうがファイルサイズが大きくなります(iOS high vs Android NORMAL)。 アップロード容量・時間を揃えるなら両方で明示してください。 RESOLUTION_960 は Android にのみあります。

公開状態

値意味
OPENEDアップロード直後に公開(既定値)
CLOSED非公開でアップロード — コンソールで審査後に公開

行動イベント

onEvent に渡される値です。分析ログ用で、一覧は今後増える可能性があるため、知らない値は無視してください。

コマンドタイミング
VIDEO_EDITOR_CLICK_CLOSE / …CONFIRMエディターを閉じる/確定
VIDEO_EDITOR_CLICK_CROP / …CROP_CONFIRM / …CROP_CANCELクロップ開始/確定/キャンセル
VIDEO_EDITOR_CLICK_FILTER / …FILTER_CONFIRM / …FILTER_CANCELフィルター開始/確定/キャンセル
VIDEO_EDITOR_CLICK_VOLUME / …VOLUME_CONFIRM / …VOLUME_CANCEL音量開始/確定/キャンセル
VIDEO_EDITOR_CLICK_PLAYBACK_SPEED / …CONFIRM / …CANCEL速度開始/確定/キャンセル
COVER_PICKER_CLICK_CAMERA_ROLL / …CLOSE / …CONFIRMカバー選択(Android)
リファレンス

エラー処理

エラーはデリゲート/ハンドラーの onError で渡されます。ショートフォーム SDK と同じコード体系で、 エディターでは主に 9300 番台(エンコード)と 9500 番台(未対応メディア)が発生します。

コード名前原因・対処
9000NOT_INITIALIZED_ACCESS_KEYsetAccessKey の前にエディターを開始した
9002NOT_INITIALIZED_CONFIGエディター設定をサーバーから受け取れていない → ネットワークを確認して再試行
9050REQUIRED_COMPONENT_ACTIVITY(Android)ComponentActivity 以外のコンテキストを渡した
9300FAILED_ENCODINGエンコード失敗 → 空き容量と元ファイルの状態を確認
9301FAILED_VIDEO_ENCODING動画のエンコード失敗
9302FAILED_COVER_ENCODINGカバー画像の生成失敗
9320WITHOUT_THUMBNAILサムネイルを生成できなかった → カバーを選び直すよう案内
9501UNSUPPORTED_MEDIA未対応のコーデック → 「この動画は使用できません」と案内
9900FAILED_NETWORKアップロード中のネットワーク失敗 → 再試行を提供
10000UNEXPECTED_ERROR分類されていない例外 → ログを添えてお問い合わせください
エンコードとアップロードには時間がかかります — 中断経路を設計してください 長い動画ほどエンコードに数十秒かかることがあります。その間にユーザーがアプリを離れたり 電話を受けたりすると流れが途切れます。失敗時に何を案内するかを onError に必ず実装してください。
リファレンス

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

項目設定場所なぜ必要か
accessKeyShoplive が発行すべての呼び出しの前提
ユーザー認証(JWT)自社バックエンドアップロードしたショートの作成者への帰属。ないと自分の投稿を再び見つけられません
UGC の利用設定Shoplive コンソールアカウントで UGC アップロードが開放されていないと成功しません
フィルタープリセットShoplive コンソール登録がないとフィルター一覧が空に見えます
審査ポリシー導入企業の運用CLOSED でアップロードするか、即時公開にするか(ミッション 2)
Info.plist の文言導入企業のアプリiOS の審査対象(インストール)
リファレンス

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

ボタンを押しても何も起きません(Android)

ComponentActivity 以外のコンテキストを渡した可能性が高いです。 エディターは activityResultRegistry で画面結果をやり取りするため、 applicationContext や旧式の Activity では動作しません(クイックスタート (Quick Start))。

長さ制限を設定したのに反映されません

単位を確認してください — iOS は秒、Android はミリ秒です。 また maxVideoDuration が minVideoDuration 以下だと、SDK は 黙って既定値に戻します(比率と長さ)。

比率の固定が iOS でしか効かないように見えます

isFixed の既定値が iOS は true、Android は false と異なります。 両方で明示的に指定してください(ミッション 2)。

フィルターボタンを有効にしたのに一覧が空です

コンソールにフィルタープリセットが登録されていません(導入企業側の設定)。

アップロードできたのに一覧に出ません

① CLOSED(非公開)でアップロードされた場合 — コンソールで公開処理が必要です。 ② isCreatedShortform = false だとショートとして登録されません。 ③ 検索条件(タグ・コレクション)がアップロード時の入力値と合っているか確認してください。

アプリの容量が大きく増えました

ネイティブのエンコードライブラリが 4 つの ABI で入ります。Android App Bundle の ABI 分割を有効にすると、端末ごとのダウンロード容量が減ります(インストール)。

両プラットフォームの画面を同じにできますか

色・アイコンのカスタマイズはAndroid 専用のため、完全に同じにはなりません。 機能の露出範囲(編集ツール・入力項目)を揃える方向で合意するのが現実的です(ミッション 2)。

アップロードしたショートをアプリで再生するには

ショートフォーム SDK ガイドを参照してください。結果の shortsId を そのまま渡せばすぐ再生できます。

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