Android 웹뷰 연동
고객사 Android(AOS) 애플리케이션의 네이티브 WebView를 활용하여 SauceLive 플레이어를 연동하는 방법을 안내합니다.
1. 연동 방식
연동 방식은 서비스 환경에 따라 SauceLive 도메인 URL 직접 연동(방식 A), 커스텀 도메인 연동(라이브러리 사용 - 방식 B/ 라이브러리 미사용 - 방식 C) 세 가지로 구분되며, 프로젝트에 적합한 방식을 선택하여 구현할 수 있습니다.
방식 A. SauceLive 도메인 URL 직접 연동
SauceLive에서 제공하는 기본 플레이어 URL을 WebView에 직접 로드합니다. 별도의 웹 개발 없이 가장 빠르고 간편하게 연동할 수 있습니다.
방식 B. 커스텀 도메인 연동 (라이브러리 사용)
고객사 도메인의 웹페이지에 SauceLive JS 라이브러리를 설치한 후, 해당 웹페이지를 WebView에 로드합니다.
👉 소스라이브 라이브러리 연동이 필요한 경우, 별도 라이브러리 가이드를 참고해 주세요. 🔗 소스라이브 라이브러리
방식 C. 커스텀 도메인 연동 (라이브러리 미사용)
고객사가 자체 웹페이지에 라이브러리를 사용하지 않고 직접 플레이어를 임베드하여 구축한 후, 해당 페이지를 WebView에 로드합니다.
2. 사전 준비 (Prerequisites) - 공통
인터넷 접속과 원활한 영상 재생을 위해 AndroidManifest.xml에 권한과 하드웨어 가속 설정을 추가해야 합니다.
<uses-permission android:name="android.permission.INTERNET" />
<application
android:hardwareAccelerated="true"
...>3. WebView 설정 (Settings) - 공통
라이브 비디오 스트리밍과 브릿지가 정상적으로 동작하기 위해서는 아래의 필수 웹 설정이 먼저 진행되어야 합니다.
import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.webkit.WebChromeClient
import android.webkit.WebView
import android.webkit.WebViewClient
@SuppressLint("SetJavaScriptEnabled")
fun setupWebView(webView: WebView) {
val settings = webView.settings
// 필수 설정
settings.javaScriptEnabled = true // JS 브릿지 및 플레이어 동작에 필수
settings.domStorageEnabled = true // 로컬 스토리지 사용 허용
settings.mediaPlaybackRequiresUserGesture = false // 사용자 터치 없이 영상 자동 재생 허용
settings.useWideViewPort = true
settings.loadWithOverviewMode = true
webView.webViewClient = object : WebViewClient() {}
webView.webChromeClient = object : WebChromeClient() {
// [중요] 영상 로딩 시 기본 재생 아이콘(검은 화면) 숨김 처리
override fun getDefaultVideoPoster(): Bitmap? {
return Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
}
}
}4. 로드 구현
선택한 연동 방식에 따라 URL을 로드합니다.
방식 A. SauceLive URL 직접 로드
val broadcastId = "YOUR_BROADCAST_ID"
val sauceLiveUrl = "[https://player.sauceflex.com/broadcast/$broadcastId](https://player.sauceflex.com/broadcast/$broadcastId)"
webView.loadUrl(sauceLiveUrl)방식 B / C. 고객사 커스텀 웹페이지 로드
라이브러리 사용 여부(방식 B, C)와 관계없이, Android 앱에서는 고객사 도메인으로 구축된 웹페이지 URL을 동일하게 로드합니다.
val clientDomainUrl = "[https://www.your-domain.com/live-player?broadcastId=YOUR_BROADCAST_ID](https://www.your-domain.com/live-player?broadcastId=YOUR_BROADCAST_ID)"
webView.loadUrl(clientDomainUrl)5. JavaScript 브릿지 연동 (Bridge Integration)
SauceLive 플레이어 내부에서 발생하는 액션을 네이티브 앱에서 처리하려면 JavascriptInterface를 등록해야 합니다.
웹에서 전달되는 JSON 객체인 payload 데이터를 파싱하여 네이티브 앱 동작을 구현할 수 있습니다.
5.1 브릿지 객체 생성
import android.content.Context
import android.webkit.JavascriptInterface
class SauceLiveBridge(private val context: Context) {
// 예시 1: 공유하기 버튼 클릭 시
@JavascriptInterface
fun sauceflexOnShare(message: String) {
// 전달받은 JSON(message)을 파싱하여 Android Native 공유 Intent 실행
}
// 예시 2: AOS 쿠폰 앱스킴 클릭 시
@JavascriptInterface
fun sauceflexCouponAosScheme(url: String) {
// 전달받은 딥링크(url) 파싱 및 인앱 화면 이동 처리
}
// 그 외 Payload 문서에 정의된 메서드들을 필요에 따라 추가 구현합니다.
}
👉 전체 Payload 규격과 메서드는 🔗 SauceLive Payload 스펙 가이드 문서를 참고해 주세요.
5.2 WebView에 브릿지 연결
// Bridge 클래스 초기화 시 context와 webView 인스턴스를 함께 넘겨줍니다.
webView.addJavascriptInterface(SauceLiveBridge(this, webView), "AndroidBridge")6. PIP(Picture-in-Picture) 구현
Android 네이티브 앱에서 화면 분할(PIP) 모드를 지원하려면 3단계 설정이 필요합니다.
Step 1. Manifest를 설정합니다.
플레이어가 표시되는 Activity에 PIP 지원 속성을 추가하고, 화면 전환 시 액티비티가 재시작되지 않도록 아래 설정을 추가합니다.
<activity
android:name=".PlayerActivity"
android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
...>
</activity>Step 2. 플레이어 내 PIP 버튼 노출 처리를 합니다.
플레이어 진입 시점(sauceflexEnter)에 네이티브 앱의 PIP를 지원 여부를 플레이어 측에 전달해 PIP 버튼을 활성화합니다.
@JavascriptInterface
fun sauceflexEnter(payload: String?) {
activity.runOnUiThread {
// 웹 플레이어에 PIP 사용 가능 상태 전달
webView.evaluateJavascript(
"(function() { window.dispatchEvent(sauceflexPictureInPictureUse(true)); })();"
) { }
}
}Step 3. 네이티브 PIP 모드로 전환합니다.
시청자가 플레이어의 PIP 버튼을 클릭했을 때(sauceflexPictureInPicture), 네이티브 앱을 PIP 모드로 전환합니다.
@JavascriptInterface
fun sauceflexPictureInPicture(payload: String?) {
activity.runOnUiThread {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val pipBuilder = PictureInPictureParams.Builder()
pipBuilder.apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// SDK 31 이상: 백그라운드 이동 시 PIP 자동 실행 옵션 및 리사이징 버그 방지
setAutoEnterEnabled(true)
setSeamlessResizeEnabled(false)
}
//비율에 맞게 PIP 뷰 생성 (로고 뜨는 거 방지)
val rect = android.graphics.Rect()
binding.webView.getGlobalVisibleRect(rect)
setAspectRatio(Rational(rect.width(),rect.height())
}
activity.enterPictureInPictureMode(pipBuilder.build())
}
}
}7. 홈플로팅(Home Floating) 구현
홈플로팅 기능은 고객사 앱 메인(홈) 화면 진입했을 때 진행 중인 라이브 방송이 있으면, 플레이어를 즉시 PIP 모드로 전환하여 미니 플레이어 형태로 노출해 시청을 유도하는 기능입니다.
홈플로팅 구현 로직은 아래와 같습니다.
- 상태 확인 : 메인 화면에서 API로 방송 진행 상태를 확인합니다.
- 액티비티 실행 :
isHomeFloating플래그를 전달하여WebViewActivity를 실행합니다. - 자동 PIP 진입 : 페이지 로드가 완료되는 시점(
onPageFinished)에 맞춰 PIP 모드로 자동 전환 및 진입하고, 플레이어 UI(버튼 등)를 숨김 처리합니다.
7.1 메인 화면에서 Activity 호출
// 라이브 중인 방송이 있을 경우
val intent = Intent(this, WebViewActivity::class.java).apply {
putExtra("IS_HOME_FLOATING", true)
}
startActivity(intent)7.2 WebViewActivity 구현 및 PIP 자동 진입 처리
WebViewClient의 onPageFinished를 오버라이드하여 웹페이지 로딩이 끝난 직후 PIP 모드를 실행합니다.
import android.app.PictureInPictureParams
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.util.Rational
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.activity.ComponentActivity
class WebViewActivity : ComponentActivity() {
private lateinit var binding: ActivityWebviewBinding
private var isHomeFloating = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityWebviewBinding.inflate(layoutInflater)
setContentView(binding.root)
// 호출 측에서 전달한 플래그 확인
isHomeFloating = intent.getBooleanExtra("IS_HOME_FLOATING", false)
binding.apply {
webView.settings.apply {
loadWithOverviewMode = true
useWideViewPort = true
javaScriptEnabled = true
javaScriptCanOpenWindowsAutomatically = true
domStorageEnabled = true
}
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
// 홈플로팅(자동 PIP 진입) 조건 확인
if (isHomeFloating) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val pipBuilder = PictureInPictureParams.Builder()
pipBuilder.apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// SDK 31 이상: 백그라운드 이동 시 PIP 자동 실행 옵션 및 리사이징 버그 방지
setAutoEnterEnabled(true)
setSeamlessResizeEnabled(false)
}
//비율에 맞게 PIP 뷰 생성 (로고 뜨는 거 방지)
val rect = android.graphics.Rect()
binding.webView.getGlobalVisibleRect(rect)
setAspectRatio(Rational(rect.width(),rect.height())
}
// 페이지 로드 완료 후 PIP 모드 진입
enterPictureInPictureMode(pipBuilder.build())
}
}
}
}
webView.loadUrl("[https://player.sauceflex.com/broadcast/YOUR_BROADCAST_ID](https://player.sauceflex.com/broadcast/YOUR_BROADCAST_ID)")
}
}
// [중요] PIP 모드 진입/해제 시 플레이어 UI 컨트롤
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean,
newConfig: Configuration
) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
if (isInPictureInPictureMode) {
// PIP 진입 시: 플레이어 내부 UI(버튼, 채팅 등) 숨김 처리
binding.webView.evaluateJavascript("dispatchEvent(window.sauceflexPictureInPictureOn);") {}
} else {
// PIP 해제(전체화면 복귀) 시: 플레이어 내부 UI 다시 노출
binding.webView.evaluateJavascript("dispatchEvent(window.sauceflexPictureInPictureOff);") {}
}
}
}8. 공유하기(Share) 기능 연동
플레이어의 링크 공유하기 아이콘을 터치하면 sauceflexOnShare 브릿지로 이벤트가 전달됩니다.
아래와 같이 전달된 payload를 파싱하여 안드로이드 기본 공유 팝업(Intent)을 호출할 수 있습니다.
@JavascriptInterface
fun sauceflexOnShare(payload: String) {
try {
val json = org.json.JSONObject(payload)
val linkUrl = json.optString("linkUrl", "")
val broadcastName = json.optString("broadcastName", "SauceLive 방송")
val shareIntent = android.content.Intent(android.content.Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(android.content.Intent.EXTRA_SUBJECT, broadcastName)
putExtra(android.content.Intent.EXTRA_TEXT, "지금 라이브 방송을 확인해보세요!\n$linkUrl")
}
context.startActivity(android.content.Intent.createChooser(shareIntent, "공유하기"))
} catch (e: Exception) {
e.printStackTrace()
}
}Updated about 11 hours ago