Shoplive 숏폼 에디터 SDK 연동 가이드
사용자가 앱 안에서 직접 숏폼을 만들어 올리게 하는 SDK입니다.
영상 선택 → 편집(자르기·크롭·필터·속도·볼륨) → 커버 선택 → 업로드까지, 화면 전체를 SDK가 제공합니다.
코드 예제는 상단의 iOS / Android 탭으로 전환해 볼 수 있습니다.
개요 & 요구사항
숏폼 에디터 SDK는 UGC(사용자 제작 숏폼) 를 위한 SDK입니다. 영상 트리밍·크롭·필터·인코딩·업로드처럼 직접 만들면 오래 걸리는 부분을 SDK가 통째로 맡고, 앱은 언제 띄울지와 끝난 뒤 무엇을 할지만 정합니다.
두 가지 사용 방식
미션 지도 — 무엇을 구현하고 싶나요?
| 구현하고 싶은 것 | 미션 |
|---|---|
| "숏폼 올리기" 버튼을 누르면 편집기가 열리고 업로드까지 끝난다 | 미션 1 · 편집기 띄우기 |
| 영상 길이·비율을 우리 정책에 맞추고, 안 쓰는 편집 도구를 숨긴다 | 미션 2 · 화면 커스터마이징 |
| 갤러리는 우리 것을 쓰고, 편집 기능만 빌린다 | 부록 · 영상 편집기만 쓰기 |
| 썸네일(커버)만 다시 고르게 한다 | 부록 · 커버 선택 |
제공 형태 (플랫폼 매핑)
| 형태 | iOS | Android |
|---|---|---|
| 전체 플로우 | 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) |
| 결과 수신 | ShopLiveShortformEditorDelegate | ShopLiveShortformEditorHandler |
present 하는 형태고,
Android는 SDK가 Activity를 띄우고 결과를 핸들러로 돌려줍니다.
커버 선택기·이미지 편집기는 Android에만 단독 컴포넌트로 공개돼 있습니다.
두 플랫폼을 동시에 붙일 때는 이 차이를 앱 레이어에서 흡수하세요.요구사항
| 항목 | iOS | Android |
|---|---|---|
| 최소 버전 | iOS 13.0+ | shoplive-media3(기본 엔진) 사용 시 minSdk 23+ · shoplive-exoplayer 사용 시 19+ |
| SDK 버전 | 1.8.12 (ShopLiveShortformEditorSDK) | 1.8.14 (shoplive-video-editor) |
| 필수 동반 모듈 | ShopliveFilterSDK · ShopliveSDKCommon | shoplive-common · shoplive-network |
| 실행 환경 | UIViewController | ComponentActivity (AppCompatActivity 포함) |
| 패키지 크기 | FFmpeg 기반 인코딩이 포함돼 앱 용량이 눈에 띄게 늘어납니다 | |
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" />
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()
ComponentActivity가 아니면 동작하지 않습니다
에디터는 activityResultRegistry로 화면 결과를 주고받습니다.
Application/Service 컨텍스트나 구식 Activity를 넘기면
9050 REQUIRED_COMPONENT_ACTIVITY가 나거나 아무 일도 일어나지 않습니다.다음 단계화면 용어 가이드 · 미션 1부터 시작 →
화면 용어 가이드
전체 플로우는 네 화면이 순서대로 이어집니다. 이후 미션·옵션 설명에서 같은 이름을 씁니다.
화면 흐름
| 단계 | 화면 | 사용자가 하는 일 | 앱이 제어할 수 있는 것 |
|---|---|---|---|
| ① | 미디어 선택 | 갤러리에서 영상을 고름 | 허용 길이 범위 · 거부 시 안내 문구 (미션 2) |
| ② | 영상 편집 | 자르기 · 크롭 · 필터 · 속도 · 볼륨 | 노출할 편집 도구 · 비율 · 길이 제한 (미션 2) |
| ③ | 커버 선택 | 썸네일로 쓸 프레임 선택 | 비율 · 크롭 버튼 노출 (iOS · Android) |
| ④ | 업로드 | 제목 · 설명 · 해시태그 · 상품 입력 후 등록 | 입력 항목 노출 · 초기값(태그·상품) · 공개 상태 (미션 2) |
편집 도구 (② 화면)
| 도구 | 하는 일 | 끄는 방법 |
|---|---|---|
| 자르기 (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의 옵션이 화면에 어떻게 반영되는지 바로 볼 수 있습니다.
- 전체 플로우 — 선택부터 업로드까지 SDK가 제공하는 기본 경로 (미션 1).
- 갤러리 + 편집기 — 앱 갤러리로 고른 영상을 편집기에만 넣는 경로 (부록).
- 편집기 단독 — 이미 가진 파일 경로/URL로 편집기만 여는 경로.
- 옵션 팝업에서 비율 · 최소/최대 길이 · 설명·태그 노출 · 편집 도구를 바꿔가며 확인합니다.
이전 ← 화면 용어 가이드다음 → 미션 1 · 편집기 띄우기
미션 1 · 편집기 띄우기
- 결과를 받을 델리게이트/핸들러를 준비합니다.
- 버튼 액션에서 에디터를 시작합니다.
- 업로드 성공·취소·에러를 각각 처리합니다.
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) // 버튼 단위 행동 로그
}
}
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 · 화면 커스터마이징
방법 ① 영상 길이 · 비율
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()
60, Android는 60_000으로 씁니다.
공통 상수를 둘 때 한쪽 단위로 정의하고 변환하세요. 값이 1000배 어긋나면
"영상이 무조건 너무 짧다/길다"는 증상으로 나타납니다.mediaPickerVideoDurationOption은 반영되지 않습니다
ShopLiveShortformEditorConfiguration의 init이 이 인자를 받기만 하고 내부에 대입하지 않아,
미디어 선택 길이 제한이 항상 기본값(3초~15분)으로 동작합니다. 선택 단계를 제한해야 한다면
ShopLiveMediaPicker 단독 경로를 쓰거나 SDK 수정을 요청하세요. 아래 설명은 의도된 설계이며,
편집 결과물 길이(min/maxVideoDuration)는 정상 반영됩니다.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()
ShopLiveShortformEditor(전체 플로우)는 내부에서 isCreatedShortform = true 와
shortformStatus = CLOSED를 덮어씁니다. 즉 아래 설정은 전체 플로우에서 무시되고 항상 비공개로 올라갑니다.
공개 상태를 직접 제어하려면 편집기 단독 경로를 쓰세요.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 에 접근합니다.
*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
입력 소스
| 소스 | iOS | Android |
|---|---|---|
| 로컬 파일 | ShopLiveVideoEditorData(videoUrl:) | ShopLiveEditorLocalData(uri) |
| 카메라 촬영 결과 | ShopLiveVideoEditorData(videoUrl:) | ShopLiveEditorCameraData(uri) |
| 원격 URL | ShopLiveVideoEditorData(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 가 공개돼 있지 않습니다.
권한 처리
사진·카메라·마이크 권한이 거부됐을 때, 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 로 편집기를 시작하세요.
NSPhotoLibraryUsageDescription 등의 설명 문구가 실제 용도와 다르면 앱 심사에서 반려될 수 있습니다.
"숏폼 제작을 위해"처럼 구체적으로 쓰세요 (설치).데이터 필드 사전
Configuration — 무엇을 어디에 넣는가
| 정하고 싶은 것 | iOS | Android |
|---|---|---|
| 화면 비율 | videoCropOption | ShopLiveVideoEditorData.aspectRatio |
| 결과물 길이 | minVideoDuration · maxVideoDuration (초) | minVideoDuration · maxVideoDuration (ms) |
| 고를 수 있는 원본 길이 | mediaPickerVideoDurationOption | — |
| 편집 도구 노출 | visibleContents.editOptions | visibleActionButton |
| 설명·태그 입력 노출 | visibleContents.isDescriptionVisible/isTagsVisible | ShopLiveShortformUploaderData.ui |
| 출력 품질·해상도 | videoOutputOption | outputVideoQuality · outputResolution |
| 업로드 공개 상태 | videoUploadOption.shortsStatus | shortformStatus |
| 숏폼 등록 여부 | videoUploadOption.isCreatedShortform | isCreatedShortform |
| 색 · 아이콘 | ShopLiveShortformEditor.*Config.global | setVideoEditorCustomize(…) |
| 업로드 초기값(상품·태그) | ShopLiveShortformUploader.setUploaderData(…) | setShortformUploaderData(…) |
비율 · 길이
| 필드 | 기본값 (iOS) | 기본값 (Android) | 설명 |
|---|---|---|---|
width : height | 9 : 16 | 9 : 16 | 크롭 비율 |
isFixed | true | false | 사용자가 비율을 바꿀 수 있는지 — 플랫폼 기본값이 다름 |
minVideoDuration | 1 (초) | 1000 (ms) | 결과물 최소 길이 |
maxVideoDuration | 60 (초) | 60000 (ms) | 결과물 최대 길이 |
| 미디어 선택 최소 | 3 (초) | — | 갤러리에서 고를 수 있는 원본 최소 길이 |
| 미디어 선택 최대 | 900 (초 = 15분) | — | 갤러리에서 고를 수 있는 원본 최대 길이 |
maxVideoDuration ≤ minVideoDuration이면 조용히 기본값(1~60초)으로 되돌립니다.
Android 는 1 미만인 값만 기본값(1000ms / 60000ms)으로 바꾸고, max ≤ min 조합은 검증하지 않습니다.
어느 쪽도 에러를 내지 않으므로 설정이 무시된 것을 눈치채기 어렵습니다. 값을 계산해 넣는다면 앱이 직접 순서를 검증하세요.결과 데이터
| 필드 | iOS ShopliveEditorResultData | Android ShopLiveEditorResultData |
|---|---|---|
| 숏폼 ID | shortsId | shortsId |
| 로컬 영상 | localVideoUrl (String) | localVideoUri (Uri) |
| 로컬 커버 | localCoverImage (UIImage) | localCoverImageUri (Uri) |
| 업로드된 영상 | remoteOriginVideoUrl | remoteOriginVideoUrl |
| 업로드된 커버 | remoteCoverImageUrl | remoteCoverImageUrl |
| 해상도 | width · height | width · 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) |
editor(ViewController)가 함께 옵니다
편집기 단독 델리게이트는 어느 편집기에서 온 이벤트인지를 함께 넘겨줍니다.
편집기를 닫거나 그 위에 알럿을 띄울 때 이 참조를 쓰세요.타입 & 상태
편집 도구 (iOS SLEditOptions)
| 값 | 도구 | Android 대응 |
|---|---|---|
.filter | 필터 | isUsedFilterButton |
.playBackSpeed | 재생 속도 | isUsedPlaybackSpeedButton |
.volume | 볼륨 | isUsedVolumeButton |
.crop | 크롭 | isUsedCropButton |
[.filter, .playBackSpeed, .volume, .crop])입니다.출력 품질 · 해상도
| 구분 | iOS | Android |
|---|---|---|
| 품질 | .normal · .high · .max (기본 .high) | NORMAL · HIGH · MAX (기본 NORMAL) |
| 해상도 | ._360 · ._480 · ._720 · ._1080 (기본 ._720) | RESOLUTION_360/480/720/960/1080 (기본 720) |
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번대(미지원 미디어) 를 만납니다.
| 코드 | 이름 | 원인 · 조치 |
|---|---|---|
9000 | NOT_INITIALIZED_ACCESS_KEY | setAccessKey 전에 에디터를 시작함 |
9002 | NOT_INITIALIZED_CONFIG | 에디터 설정을 서버에서 못 받음 → 네트워크 확인 후 재시도 |
9050 | REQUIRED_COMPONENT_ACTIVITY | (Android) ComponentActivity가 아닌 컨텍스트를 넘김 |
9300 | FAILED_ENCODING | 인코딩 실패 → 저장 공간·원본 파일 상태 확인 |
9301 | FAILED_VIDEO_ENCODING | 영상 인코딩 실패 |
9302 | FAILED_COVER_ENCODING | 커버 이미지 생성 실패 |
9320 | WITHOUT_THUMBNAIL | 썸네일을 만들지 못함 → 커버를 다시 고르게 안내 |
9501 | UNSUPPORTED_MEDIA | 지원하지 않는 코덱 → "이 영상은 사용할 수 없어요" 안내 |
9900 | FAILED_NETWORK | 업로드 중 네트워크 실패 → 재시도 제공 |
10000 | UNEXPECTED_ERROR | 분류되지 않은 예외 → 로그와 함께 문의 |
onError에 반드시 구현하세요.고객사 직접 설정 항목
트러블슈팅 & 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 를
그대로 넘겨 바로 재생할 수 있습니다.