Android(aos) Integration
SauceLive Player Android WebView Integration Guide
This is the page displayed when accessing the live URL, and users can watch the live broadcast.
If the broadcast hasn't started, the user is redirected to the schedule page. Once the live starts, the player page loads immediately.
How to Integrate with Android WebView
- Supports login page URL and redirection back to the SauceLive page.
- Add the player URL to the WebView.
Partner's site Login Page URL
- The URL format is: https://player.sauceflex.com/broadcast/{Live Link}?accessToken={Generated accessToken}
- Redirection back to the SauceLive page after login is done using a query string like
returnUrl
- After login, the accessToken generated through server integration can be attached to the returnUrl via a query string and used to navigate to the desired page.
Android Bridge Communication Guide
Project Setup
STEP 1. Create a new project using Android Studio.
STEP 2. Add permissions in AndroidManifest.xml
for bridge usage.
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
WebView Setup
STEP 1. Add a WebView
to activity_webview.xml
.
<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
STEP 2. Set up the bridge in WebViewActivity.java
or WebViewActivity.kt
.
MainActivity : AppCompatActivity() {
private lateinit var webView: WebView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
webView = findViewById(R.id.webview)
// JavaScript Activation
webView.settings.javaScriptEnabled = true
// JavaScript Add interface
webView.addJavascriptInterface(WebAppInterface(this), "sauceflex")
// Player Url load
webView.loadUrl("https://player.sauceflex.com/broadcast/{Broadcast Link}?accessToken={Generated accessToken}")
}
class WebAppInterface(private val mContext: Context) {
// Callback on live entry – automatically called when the page is loaded
@JavascriptInterface
fun sauceflexBroadcastStatus() {
handler.post {
// Add processing code here
}
}
// Callback on live status change – automatically called when the live status changes
@JavascriptInterface
fun sauceflexBroadcastStatus(message: String) {
handler.post {
// Add processing code here
}
}
// Callback on exit
@JavascriptInterface
fun sauceflexMoveExit() {
handler.post {
// Add processing code here
}
}
// Callback on login
@JavascriptInterface
fun sauceflexMoveLogin() {
handler.post {
// Add processing code here
}
}
// Callback on product click
@JavascriptInterface
fun sauceflexMoveProduct(message: String) {
handler.post {
// Add processing code here
}
}
// Callback on banner click
@JavascriptInterface
fun sauceflexMoveBanner(message: String) {
handler.post {
// Add processing code here
}
}
// Callback on coupon click
@JavascriptInterface
fun sauceflexMoveCoupon(message: String) {
handler.post {
// Add processing code here
}
}
// Callback on reward completion
@JavascriptInterface
fun sauceflexMoveReward(message: String) {
handler.post {
// Add processing code here
}
}
// Callback on share button click
@JavascriptInterface
fun sauceflexOnShare(message: String) {
handler.post {
// Add processing code here
}
}
// Callback on PIP toggle button click (player overlay elements are automatically hidden when switching to PIP)
@JavascriptInterface
fun sauceflexPictureInPicture () {
handler.post {
// Add processing code here
}
}
// WebView reloading
@JavascriptInterface
fun sauceflexWebviewReloading() {
handler.post {
// Add processing code here
}
}
}
}
class WebViewActivity extends AppCompatActivity {
private WebView webView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
webView = findViewById(R.id.webview);
// JavaScript 활성화
webView.getSettings().setJavaScriptEnabled(true);
// JavaScript 인터페이스 추가
webView.addJavascriptInterface(new WebAppInterface(this), "sauceflex");
// Asset 폴더의 HTML 파일 로드
webView.loadUrl("https://player.sauceflex.com/broadcast/{방송주소}?accessToken={생성된accessToken}&returnUrl={로그인페이지등}");
}
public class WebAppInterface {
Context mContext;
WebAppInterface(Context c) {
mContext = c;
}
// 방송 입장 시 콜백 처리 - 페이지 진입 시 자동으로 호출
@JavascriptInterface
public void sauceflexEnter() {
handler.post(new Runnable() {
@Override
public void run() {
// 처리 코드를 여기에 추가
}
});
}
// 방송 상태 변경 시 콜백 처리 - 방송 상태 변경 발생 시 자동으로 호출
@JavascriptInterface
public void sauceflexBroadcastStatus(String message) {
final String finalMessage = message;
handler.post(new Runnable() {
@Override
public void run() {
// 처리 코드를 여기에 추가
}
});
}
// 나가기 시 콜백 처리
@JavascriptInterface
public void sauceflexMoveExit() {
handler.post(new Runnable() {
@Override
public void run() {
// 처리 코드를 여기에 추가
}
});
}
// 로그인 시 콜백 처리
@JavascriptInterface
public void sauceflexMoveLogin() {
handler.post(new Runnable() {
@Override
public void run() {
// 처리 코드를 여기에 추가
}
});
}
// 상품 클릭 시 콜백 처리
@JavascriptInterface
public void sauceflexMoveProduct(String message) {
final String finalMessage = message;
handler.post(new Runnable() {
@Override
public void run() {
// 처리 코드를 여기에 추가
}
});
}
// 배너 클릭 시 콜백 처리
@JavascriptInterface
public void sauceflexMoveBanner(String message) {
final String finalMessage = message;
handler.post(new Runnable() {
@Override
public void run() {
// 처리 코드를 여기에 추가
}
});
}
// 쿠폰 클릭 시 콜백 처리
@JavascriptInterface
public void sauceflexMoveCoupon(String message) {
final String finalMessage = message;
handler.post(new Runnable() {
@Override
public void run() {
// 처리 코드를 여기에 추가
}
});
}
// 리워드 완료 시 콜백 처리
@JavascriptInterface
public void sauceflexMoveReward(String message) {
final String finalMessage = message;
handler.post(new Runnable() {
@Override
public void run() {
// 처리 코드를 여기에 추가
}
});
}
// 공유하기 클릭 시 콜백 처리
@JavascriptInterface
public void sauceflexOnShare(String message) {
final String finalMessage = message;
handler.post(new Runnable() {
@Override
public void run() {
// 처리 코드를 여기에 추가
}
});
}
// PIP 전환 버튼 클릭 시 콜백 처리 (PIP 전환 시에 플레이어 위 요소들은 자동으로 미표시됩니다.)
@JavascriptInterface
public void sauceflexPictureInPicture() {
handler.post(new Runnable() {
@Override
public void run() {
// 처리 코드를 여기에 추가
}
});
}
// 웹뷰 리로딩
@JavascriptInterface
public void sauceflexWebviewReloading() {
handler.post(new Runnable() {
@Override
public void run() {
// 처리 코드를 여기에 추가
}
});
}
}
}
How to Implement PIP Mode on Android
Introduction to PIP Mode
Picture-in-Picture (PIP) is a special multi-window mode introduced in Android 8.0 (API level 26) that allows a video to play in a small window while the user interacts with other apps. It’s commonly used for video playback and enhances multitasking by keeping video content accessible in a floating window.
To implement PIP:
- Declare support for PIP in the activity.
- Enter PIP mode when appropriate (e.g., when the user navigates away).
- Hide unnecessary UI components while in PIP mode.
The floating window appears in a corner of the screen and supports drag, fullscreen, and close actions. The transition to PIP can be triggered by system events (like Home button press) or custom app logic (e.g., navigating to another section while playing video).
Player PIP Button Display Options
There are two ways to display the PIP button in the player:
Config-based Setting
Mobidoo can expose the PIP button based on partner configuration.
Partners can request this setting during configuration (e.g., setting login URL, close button redirect, etc.).
Manual Setting
Partners can manually control the display of the PIP button, offering more flexibility and control.
(See details below.)
- Implement JavaScript communication between the app and WebView.
At the time of thesauceflexEnter
event, use the following script to display the PIP button. - If the player takes time to load in the WebView, executing JavaScript during the
sauceflexEnter
event may not work properly. In such cases, add a slight delay before calling the script to ensure proper execution.
// If PIP usage is set to true, the PIP button will be displayed.
myWebView?.evaluateJavascript( "(function() { window.dispatchEvent(sauceflexPictureInPictureUse(true)); })();") { }
//If PIP usage is set to false, the PIP button will not be displayed.
myWebView?.evaluateJavascript( "(function() { window.dispatchEvent(sauceflexPictureInPictureUse(false)); })();") { }
How to Enter PIP Mode
By default, the system does not automatically support Picture-in-Picture (PIP) for apps. To enable PIP in your app, you must declare video activities in the manifest by setting android:supportsPictureInPicture
to true
.
Additionally, to prevent your activity from restarting when the layout changes during PIP mode transition, specify that the activity handles layout configuration changes.
PIP Mode Configuration
Add the following attributes to the activity component in AndroidManifest.xml
.
<activity android:name="WebViewActivity"
android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
...
Enabling PIP Mode
- Enter PIP mode by pressing the PIP button in the player — triggered via the sauceflexPictureInPicture bridge.
@JavascriptInterface // When the PIP toggle button is clicked and the view switches to PIP mode, currently visible components are automatically set to 'GONE'.
(Examples of GONE content: like buttons or other buttons displayed over the player)
fun sauceflexPictureInPicture () {
handler.post {
if (packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
val pipBuilder = PictureInPictureParams.Builder()
pipBuilder.apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
setAutoEnterEnabled(true)
setSeamlessResizeEnabled(false) // Improved flickering issue during resizing.
}
setAspectRatio(Rational(9,16)) // Created PIP view in a 9:16 aspect ratio.
}
enterPictureInPictureMode(pipBuilder.build())
}
}
}
- Entered PIP mode when the user presses the home key or moves the app to the background.
override fun onUserLeaveHint() {
super.onUserLeaveHint()
if (packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
val pipBuilder = PictureInPictureParams.Builder()
pipBuilder.apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
setAutoEnterEnabled(true)
setSeamlessResizeEnabled(false) // Improved flickering issue during resizing.
}
setAspectRatio(Rational(9,16)) // Created PIP view in a 9:16 aspect ratio.
}
enterPictureInPictureMode(pipBuilder.build())
}
}
Player UI Hiding in PIP Mode
When entering PIP mode via the sauceflexPictureInPicture
bridge, the player UI is automatically hidden.
However, if PIP mode is entered through other means, you must manually call JavaScript to hide the UI elements.
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean,
newConfig: Configuration
) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
if(isInPictureInPictureMode) {
binding.webView.evaluateJavascript("dispatchEvent(window.sauceflexPictureInPictureOn);") {
// do something
}
} else {
binding.webView.evaluateJavascript("dispatchEvent(window.sauceflexPictureInPictureOff);") {
// do something
}
}
}
PIP Mode Resizing Issue
PIP mode uses Android’s built-in system view, and its default behaviors are controlled by the system.
When the user performs actions like double-tap or pinch zoom in/out, the PIP window may resize, causing flickering in the WebView.
This issue can be mitigated by calling setSeamlessResizeEnabled(false).
⚠️ This option is available only on Android 12 and above.
For devices running lower versions, you may need to implement a custom view overlay instead of using the system PIP view.
홈플로팅 구현
Step 1. Add WebViewActivity to AndroidManifest.xml
(* You may rename the Activity as needed)
<activity android:name="WebViewActivity"
android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
...
Step 2. WebViewActivity Configuration – Settings for entering PIP mode when the Activity is launched
class WebViewActivity : ComponentActivity(){
private lateinit var binding: ActivityWebviewBinding
private var isHomeFloating = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityWebviewBinding.inflate(layoutInflater)
setContentView(binding.root)
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)
//The isHomeFloating variable is required when using the same Activity for both PIP and Home Floating functionality.
// If not applicable, you can remove the condition and allow unconditional execution to enable Home Floating mode.
if (isHomeFloating) {
// PIP mode is supported from SDK version 26 and above – if this Activity is used for both purposes, conditional handling is required.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val pipBuilder = PictureInPictureParams.Builder()
pipBuilder.apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
setAutoEnterEnabled(true). // From SDK version 31 and above, options are available to support automatic PIP activation (e.g., when moving to background via Home button).
setSeamlessResizeEnabled(false) // Option to resolve PIP resizing issue
}
setAspectRatio(Rational(9, 16))
}
enterPictureInPictureMode(pipBuilder.build()) // Enter PIP mode
}
}
}
}
webView.loadUrl("Broadcast URL")
}
}
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean,
newConfig: Configuration
) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
if (isInPictureInPictureMode) {
binding.webView.evaluateJavascript("dispatchEvent(window.sauceflexPictureInPictureOn);") {
}
} else {
binding.webView.evaluateJavascript("dispatchEvent(window.sauceflexPictureInPictureOff);") {
}
}
}
}
Step 3. Launch WebViewActivity
from the Activity where you want to run Home Floating.
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
startActivity(Intent(this,WebViewActivity::class.java))
}
Share Function Implementation
Android Default System Share View
You can implement the share button functionality using the sauceflexOnShare bridge.
This allows triggering the native Android system share UI from within the WebView when a share event occurs.
class WebAppInterface(private val context: Context) {
private val handler = Handler()
...
@JavascriptInterface // Sharing
fun sauceflexOnShare(message: String) {
handler.post {
try {
// message Format - Json String
val jsonObject = JSONObject(message)
val shareURL = jsonObject.getString("linkUrl")
// Can be replaced with the following code when using shortUrl.
// val shareURL = jsonObject.getString("shortUrl")
val intent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, shareURL)
}
context.startActivity(Intent.createChooser(intent, "Saucelive Player")) //Enter a custom title for sharing instead of “Saucelive Player"
} catch (e: Exception) {
e.printStackTrace()
// Handle errors
}
}
}
...
}
public class WebAppInterface {
private Context context;
private Handler handler;
// Constructor to initialize the context and handler
public AndroidBridge(Context context) {
this.context = context;
this.handler = new Handler();
}
...
// 공유하기
@JavascriptInterface
public void sauceflexOnShare(String message) {
final String finalMessage = message;
handler.post(new Runnable() {
@Override
public void run() {
try {
// message 형식 - Json String
JSONObject jsonObject = new JSONObject(message);
String shareURL = jsonObject.getString("linkUrl");
// shortUrl 사용 시 아래 코드로 대체 가능
// String shareURL = jsonObject.getString("shortUrl");
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, shareURL);
context.startActivity(Intent.createChooser(intent, "Share via"));
} catch (Exception e) {
e.printStackTrace();
// 오류 처리
}
}
});
}
...
}

linkUrl ( Display the specified text in the system view. )

shortUrl use ( Display as the title of the live broadcast. )
Notes for Integrating the Player in WebView
If you want the video to play in Android WebView without displaying the play icon, please refer to and apply the following code. (This guideline is intended for the partner's AOS app development team.)
Updated 13 days ago