Guides

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

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 the sauceflexEnter 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 사용

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

shortUrl 사용

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.)