Shoplive Developers
한국어KOEnglishEN日本語JA

Shoplive 숏폼 SDK 연동 가이드

쇼핑 숏폼(Shorts)을 앱에 붙이는 방법을 구현하고 싶은 케이스(미션) 단위로 안내합니다. 풀스크린 재생 · 컬렉션 임베드 · 플로팅 프리뷰 · 상품/공유 연동까지 필요한 방식만 골라 적용하면 됩니다.
코드 예제는 상단의 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

// ① 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")),
]

// ② CocoaPods — Podfile
// pod 'ShopliveShortformSDK', '1.8.13'
// pod 'ShopliveSDKCommon',    '1.8.13'

// ③ 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 담당자 또는 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)

이 두 줄이면 숏폼 컬렉션을 불러와 세로 풀스크린으로 재생하고, 상품 카드·좋아요·댓글·공유 오버레이까지 동작합니다. 각 줄의 상세: ① 사용자 정보 연결 · ② 미션 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()
도식 이미지 추가 예정 sdk3 가이드와 같은 번호 주석 스크린샷(ko/en/ja)은 준비되는 대로 이 자리에 들어갑니다. 번호 ①~⑩ 은 이미지가 붙어도 그대로 유지됩니다.

컬렉션 (앱 안 임베드)

앱의 기존 화면에 붙이는 컬렉션입니다. 카드 한 장에 무엇을 보여줄지 옵션으로 켜고 끕니다 (미션 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가 호출되어 있는지 확인합니다 (빠른 시작).
  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 완료 (조회 조건 이해)

컬렉션은 세 가지 모양이 있습니다 — 카드형(그리드) · 세로형 · 가로형(캐러셀). 세 타입 모두 같은 조회 조건과 같은 표시 옵션을 씁니다. 모양만 고르면 됩니다.

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)
    // … 오토레이아웃 제약 …

    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)로 먼저 조회한 뒤 넘기세요 — 홈 화면에서 여러 캐러셀을 한 번의 조회로 채울 때 유리합니다.

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단계)
setAuthToken · setUser 는 사용자 정보를 넘기는 것이지, 사용자를 인증하는 것이 아닙니다 로그인(아이디·비밀번호, 소셜 로그인 등)은 전적으로 앱이 합니다. 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: .male,
    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.MALE)
    }
)

// ③ 비로그인 — 호출하지 않아도 됩니다

// 로그아웃 — 사용자 정보 초기화 (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"}

서명 예시입니다. 두 코드는 같은 토큰을 만듭니다 — 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 (저장해 둔 토큰을 다시 쓰지 않음)
  • 로그아웃 시 — 사용자 정보를 초기화해야 이전 사용자 기록이 섞이지 않음

완료 확인

  • 로그인 후 좋아요를 누르고 앱을 재실행해도 눌린 상태가 유지된다
  • 로그인 후 댓글 작성이 차단되지 않는다
  • 로그아웃 → 사용자 정보를 초기화한 뒤 다시 들어가면 좋아요·북마크 상태가 초기화된다(게스트로 시청된다)
라이브(Player) SDK와 같은 사용자 정보를 씁니다 라이브(Player) SDK를 이미 붙였다면 같은 JWT · 같은 사용자를 그대로 씁니다. 두 SDK를 함께 쓰는 앱은 인증 설정을 한 곳에서 한 번만 하면 됩니다.

이전 ← 미션 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가 앱에 콜백을 넘깁니다. 콘솔·웹이 공유 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 커스터마이징 & 노출 제어

목표우리 서비스에 없는 기능(예: 댓글)의 버튼을 숨기고, 카드 표시 항목·영상 채움 방식 맞추기
사전 준비미션 1 · 미션 2 완료

방법 ① 상세 화면 버튼 숨기기

좋아요·댓글·북마크·공유는 개별로 끌 수 있습니다. 우리 서비스에 없는 기능은 꺼두는 편이 혼란이 적습니다.

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) 연동

앱 안의 웹뷰에서 숏폼을 띄우는 구성입니다. 웹 페이지의 숏폼 호출을 네이티브 SDK가 받아 처리합니다.

// 웹뷰 생성 직후 연결
ShopLiveShortform.BridgeInterface.connect(myWebView)

// 숏폼 풀스크린 위에 앱 화면(예: 상품 상세)을 띄울 때
ShopLiveShortform.BridgeInterface.present(viewController: productDetailVC)

// 화면을 벗어날 때
ShopLiveShortform.BridgeInterface.disconnect()
// 웹뷰 생성 직후 브리지를 자바스크립트 인터페이스로 등록
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건이면 조건을 하나씩 빼며 좁혀보세요.

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 대신 액티비티 전달
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 발급모든 호출의 전제 (빠른 시작)
숏폼 콘텐츠 등록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와 사용자 인증은 공통 모듈에서 한 번만 설정하면 두 SDK가 함께 씁니다. 단, 두 SDK의 버전대를 맞춰 받으세요.

Shoplive 숏폼 SDK 연동 가이드 · iOS 1.8.13 / Android 1.8.14 기준 · 사용 가이드