Shoplive Developers
한국어KOEnglishEN日本語JA

Shoplive 숏폼 에디터 SDK 연동 가이드

사용자가 앱 안에서 직접 숏폼을 만들어 올리게 하는 SDK입니다. 영상 선택 → 편집(자르기·크롭·필터·속도·볼륨) → 커버 선택 → 업로드까지, 화면 전체를 SDK가 제공합니다.
코드 예제는 상단의 iOS / Android 탭으로 전환해 볼 수 있습니다.

시작하기

개요 & 요구사항

숏폼 에디터 SDK는 UGC(사용자 제작 숏폼) 를 위한 SDK입니다. 영상 트리밍·크롭·필터·인코딩·업로드처럼 직접 만들면 오래 걸리는 부분을 SDK가 통째로 맡고, 앱은 언제 띄울지와 끝난 뒤 무엇을 할지만 정합니다.

바로 시작하기 →

두 가지 사용 방식

보여주는 쪽은 별도 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 네 ABI의 .so가 들어가므로, 용량이 중요하다면 ABI 분할(App Bundle) 을 켜세요.
시작하기

설치

// ① 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 를 함께 추가합니다.

// ② CocoaPods — Podfile
// pod 'ShopLiveShortformEditorSDK', '1.8.12'   # ShopliveFilterSDK 포함
// pod 'ShopliveSDKCommon',          '1.8.12'

// ③ 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)

먼저 준비하세요 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부터 시작 →

화면 & 데모

화면 용어 가이드

전체 플로우는 네 화면이 순서대로 이어집니다. 이후 미션·옵션 설명에서 같은 이름을 씁니다.

화면 흐름

단계화면사용자가 하는 일앱이 제어할 수 있는 것
①미디어 선택갤러리에서 영상을 고름허용 길이 범위 · 거부 시 안내 문구 (미션 2)
②영상 편집자르기 · 크롭 · 필터 · 속도 · 볼륨노출할 편집 도구 · 비율 · 길이 제한 (미션 2)
③커버 선택썸네일로 쓸 프레임 선택비율 · 크롭 버튼 노출 (iOS · Android)
④업로드제목 · 설명 · 해시태그 · 상품 입력 후 등록입력 항목 노출 · 초기값(태그·상품) · 공개 상태 (미션 2)
도식 이미지 추가 예정 sdk3 가이드와 같은 번호 주석 스크린샷(ko/en/ja)은 준비되는 대로 이 자리에 들어갑니다. 번호 ①~④ 는 이미지가 붙어도 그대로 유지됩니다.

편집 도구 (② 화면)

도구하는 일끄는 방법
자르기 (Trim)시작·끝 지점을 잡아 길이 조절항상 노출 (길이 범위만 조절)
크롭 (Crop)화면 비율에 맞춰 잘라내기iOS editOptions · Android isUsedCropButton
필터 (Filter)콘솔에 등록된 색보정 프리셋 적용iOS editOptions · Android isUsedFilterButton
속도 (Playback Speed)배속 조절iOS editOptions · Android isUsedPlaybackSpeedButton
볼륨 (Volume)원본 소리 크기 조절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 · 사용자 식별 (빠른 시작)
  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는 액티비티만 넘깁니다. 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초로 잘라내게 하려면 두 값을 다르게 주세요.
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 미만인 값만 기본값(1000ms / 60000ms)으로 바꾸고, 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 기본값은 네 가지 전부([.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). 업로드 용량·시간을 맞추려면 양쪽 모두 명시하세요. Android에만 RESOLUTION_960이 있습니다.

공개 상태

값의미
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로는 동작하지 않습니다 (빠른 시작).

길이 제한을 설정했는데 반영되지 않아요

단위를 확인하세요 — 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 기준 · 사용 가이드