Implement Mobile Floating

1. iOS Player WebView

Sample code
iOS PIP Sample Source Code

Developing Floating View and communicating between apps and the web using Bridge
It is independent of Apple's PIP capabilities and should be preceded by the development of custom floating views.

Based on this, you may need to solve the value by exchanging specific values between the app and the web.

This is the way to call Bridge. It's a bridge call method. Currently, the Saucelive player has completed the development of Bridge.

Using this, the app will implement interworking between WebViews.

Implementing a floating custom view

  • When running players in the app, it operates on a customized floating view similar to the PIP function provided by Apple.
  • Mobidoo service uses an open source called PIPKit (https://github.com/Kofktu/PIPKit ) to implement a floating function. The above open source is not standard, and the floating function is freely implemented.
  • Please refer to the image below for PIPKit.

Bridge & Message

This is also introduced in the SauceLive Player Interworking Guide.

extension ViewController: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController,
                                    didReceive message: WKScriptMessage) {
        switch (message.name) {
            case "sauceflexEnter":   // first render
                print("sauceflexEnter")
                break
 
            case "sauceflexMoveExit":   // exit button click
                print("sauceflexMoveExit")
                break
 
            case "sauceflexMoveLogin":   // login popup check
                print("sauceflexMoveLogin")
                break
 
            case "sauceflexMoveProduct":   //click to product
                print("sauceflexMoveProduct")
                print(message.body)
                break
 
            case "sauceflexOnShare":   // to share button
                print("sauceflexOnShare")
                print(message.body)
                break
 
            case "sauceflexPictureInPicture":  
 // Click the IP Switch button to automatically process component GONE processing that is being shown when switching the PIP view. (Like buttons, etc. shown above the player)
                print("sauceflexPictureInPicture")
                print(message.body)
                break
            
              // case "sauceflexPictureInPictureNoEvent":  
 // When you click the PIP switch button, you must call the GONE processing event script directly for the player components you are showing when switching the PIP view.
                //print("sauceflexPictureInPictureNoEvent")
                //print(message.body)
                //break
            // *When linking PIP button bridges, you must declare and use only one of the souceflexPictureInPicture or sauceflexPictureInPictureNoEvent.

            case "sauceflexMoveBanner":  
 //BannerId (banner unique ID), linkUrl (URL registered in banner), broadcastIdx (broadcast number) when clicking on banner
                print("sauceflexMoveBanner")
                print(message.body)
                break
 
            default:
                print("message.name \\(message.name) not handled.")
        }
    }
}

Hand over events when switching modes

When switching to PIP mode or exiting PIP mode, WebView Player must forward information to WebView to enable GONE/VISIBLE processing of components drawn on the screen.

To do this, you need to hand over the event when you switch mode as shown below.

  • When switching PIP view from player-wide view
if #available(iOS 14.2, *) {
self.playerView.player?.pictureInPictureController?.canStartPictureInPictureAutomaticallyFromInline = true
// If the point is a PIP mode transition scenario, use JavaScript calls, WKWebView's value JavaScript function.
// native call -> js (this is available at any time)
myWebView?.evaluateJavascript ("(function() { window.dispatchEvent(sauceflexPictureInPictureOn); })();" ) { }
}
  • When switching from player PIP view to full view
if isActive == true {
   self.playerView.player?.pictureInPictureController?.stopPictureInPicture()
   // If the point is a PIP mode to Full View mode transition scenario, use JavaScript calls, WKWebView's value JavaScript function.
   // You can also implement it in an annotated manner.
   // let myJsFuncName = "mergeAppUserInfo('\(myUserid)','\(myVersion!)','ios','\(iosVersion)')"
   // native call -> js (this is available at any time)
   // mainWebView.evaluateJavaScript(myJsFuncName) { (result, error) in
   // if let result = result{
  // print(result)
// }
// }
myWebView?.evaluateJavascript( "(function() { window.dispatchEvent(sauceflexPictureInPictureOff); })();" ) { }
}

# 2. Android Player WebView

Sample code

Android PIP

Introducing PIP Mode

In Android 8.0 (API level 26), you can run an activity in PIP mode. PIP is a special type of multi-window mode used primarily for video playback. Users can view videos from a small window pinned to the corner of the screen as they navigate between apps on the main screen or navigate through content.
PIP leverages the multi-window API available on Android 7.0 to provide a fixed video overlay window. To add PIP to the app, you must register activities that support PIP, put the activities into PIP mode if necessary, hide UI elements and allow video playback to continue when the activities are in PIP mode.
The PIP window appears at the top of the screen in the corner selected by the system. You can drag the PIP window to another location. Tap the window to display two special controls. Full screen transition (center of window) and close button (the 'X' in the upper right corner).

Controls when the current activity enters PIP mode in the app. Here are some examples.

  • If a user selects another app by tapping the Home or Recent button, the activity may enter PIP mode. This is the same way you can still see the path on Google Maps while you're running other activities simultaneously.
  • You can switch the video to PIP mode when users browse other content while watching it.
  • You can switch the video to PIP mode while the user is watching the end of the content episode. The main screen displays promotional or summary information about the next episode of the series.
  • Apps can provide a way to queue additional content while users watch videos. The content selection activity is displayed on the main screen while the video continues to play in PIP mode.

How to set PIP button exposure

There are two ways to expose the PIP button within the player.

  1. How to set MobyDoo to expose PIP buttons in customer settings. Register PIP button exposure when setting the customer-specific configuration (ex, Login URL registration, Close button movement URL registration, etc.).
    This can lead to problems synchronizing with the customer's app updates. The reason is that PIP button exposure is exposed at the same time as distribution, so it can be a problem if it has to be distributed with the customer's app update.
  2. The customer can directly process the exposure of the PIP button exposure. In this case, direct exposure management is performed, making it easier to do so (see description below)
  • Implementing JavaScript communication between apps and Webview
    You can process the exposure of the PIP button as follows at the time of the saucflex enter at the time of handling the event.
// Exposing PIP buttons when setting PIP usage true
myWebView?.evaluateJavascript(
"(function() { window.dispatchEvent(sauceflexPictureInPictureUse(true)); })();"
) { }
// PIP button not exposed when setting false using PIP
myWebView?.evaluateJavascript(
"(function() { window.dispatchEvent(sauceflexPictureInPictureUse(false)); })();"
) { }

3. Implementation within the Native App

Native Player 구현

In earnest, the system does not automatically support PIP for apps. To support PIP in the app, you must register a video activity with Manifest by setting 'android:supportPictureInPicture' to 'true'.

It also specifies that the activity handles layout configuration changes so that the activity does not restart when the layout changes during a PIP mode transition.

Setting PIP Mode

  • Add properties to the activity components of the manifest
<activity android:name="VideoActivity"
    android:supportsPictureInPicture="true"
    android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
    ...
  • Enabling PIP Mode
var pipWidth = 900
var pipHeight = 1600
 
var pipBuilder = PictureInPictureParams.Builder()
pipBuilder.setAspectRatio(Rational(pipWidth, pipHeight))
(activity as BaseVideoActivity).enterPictureInPictureMode(pipBuilder.build())
  • PIP mode state change listener
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
   super.onPictureInPictureModeChanged(isInPictureInPictureMode)
   if (isInPictureInPictureMode) {
      normalToPip()
   } else {
      pipToNormal()
   }
}

Enable single playback activity in PIP mode (avoid redundant VOD/broadcast activity calls)

In the app, while the video playback activity is in PIP mode, the user may select a new video when navigating content on the main screen. Instead of running a new activity that may confuse you, play a new video of an existing playback activity in full screen mode.
To use a single activity for a video playback request and enter or exit PIP mode as needed, set 'android:launchMode' of the activity to 'singleTask' in Manifest.

  • Add the following properties to the activity components of the manifest
android:launchMode= "singleTask"

Clear App Remaining in Backstack Settings

This is a setting to prevent the activity that runs on the Recent List when you press and hold the Home key.

  • Add the following properties to the activity components of the manifest
android:excludeFromRecents= "true"

PIP mode life cycle

  • From a normal screen to a PIP screen: Running → onPause
  • Clear PIP Screen: onPause → onStop → on Destroy
  • PIP screen to normal screen: onPause → onResume → Running

Implementing WebView Player (Communicating App and Web using Bridge)

You may need to resolve the value by exchanging specific values between the app and the web. What you need at this point is the way you call the Bridge.

Currently, the Saucelive player has completed the development of Bridge. Using this, the app will implement interworking between WebViews.

First, you need to add JavascriptInterface. You can declare the interface name as "sauceflex".

### Bridge & Message

This is also introduced in the Saucelive Player Interworking Guide. The contents below were introduced based on the existing documents.

class AndroidBridge(private val activity: MainActivity) {
    private val handler = Handler()
  
    @JavascriptInterface
    fun sauceflexEnter() {
        handler.post {
            Toast.makeText(activity, "sauceflexEnter", Toast.LENGTH_SHORT).show()
        }
    }
  
    @JavascriptInterface  
    fun sauceflexMoveExit() {
        handler.post {
            Toast.makeText(activity, "sauceflexMoveExit", Toast.LENGTH_SHORT).show()
        }
    }
 
    @JavascriptInterface   
    fun sauceflexMoveLogin() {
        handler.post {
            Toast.makeText(activity, "sauceflexMoveLogin", Toast.LENGTH_SHORT).show()
        }
    }
  
    @JavascriptInterface  
    fun sauceflexMoveProduct(message: String) {
        handler.post {
            Toast.makeText(activity, "sauceflexMoveProduct \\n $message", Toast.LENGTH_SHORT).show()
        }
    }
 
    @JavascriptInterface 
    fun sauceflexOnShare(message: String) {
        handler.post {
            Toast.makeText(activity, "sauceflexOnShare \\n $message", Toast.LENGTH_SHORT).show()
        }
    }
  
   @JavascriptInterface 
   fun sauceflexPictureInPicture () {
       handler.post {
           Toast.makeText(activity, "sauceflexPictureInPicture", Toast.LENNGTH_SHORT).show()
       }
    }
  
    //@JavascriptInterface 
   //fun sauceflexPictureInPictureNoEvent () {
       handler.post {
           //Toast.makeText(activity, "sauceflexPictureInPictureNoEvent", Toast.LENNGTH_SHORT).show()
       }
    //}
 
    @JavascriptInterface  
    fun sauceflexMoveBanner(message: String) {
        handler.post {
            Toast.makeText(activity, "sauceflexMoveBanner \\n $message", Toast.LENGTH_SHORT).show()
        }
    }
}
  

Hand over events when mode is switched

The next step is to pass information to WebView when switching to PIP mode or when exiting PIP mode, so that WebView Player can process GONE/VISIBLE components drawn on the screen. To do this, you need to hand over the event when you switch mode as shown below.

  • When switching the PIP view from the player-wide view (calls to the sauceflexPictureInPictureOn only need to be made when implementing the PIP transition via the sauceflexPictureInPictureNoEvent bridge)
    When implemented via a saucesflexPictureInPicture call, saucesflexPictureInPictureOn enables components to automatically process GONE/VISIBLE without overcoming events.
fun minimize() {
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      myWebView?.evaluateJavascript ("(function() { window.dispatchEvent(sauceflexPictureInPictureOn); })();" ) { }
   //…
   var pipBuilder = dPictureInPictureParams.Builder()
       .setAspectRatio(Rational(900, 1600))
       .build()
   enterfPictureInPictureMode (pipBuilder)
   }
}
  • When switching from player PIP view to full view
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {   // PIP 보기에서 FULL 보기 전환시
   super.onPictureInPictureModeChanged(isInPictureInPictureMode)
       if (isInPictureInPictureMode) {
           normalToPip()
       } else {
          myWebView?.evaluateJavascript( "(function() { window.dispatchEvent(sauceflexPictureInPictureOff); })();" ) { }
          pipToNormal()
       }
}

iFrame Player implementation (communicating iFrame with App using Bridge)

Based on the contents, if an error occurs during the JavaScript communication (dispatchEvent) attempt, please connect the communication between the app and iFrame using the method below.

  • Case : When the window of the window.dispatchEvent occurs in the window of the customer's web as it is developed as an iframe of the customer's player
const iframe = document.querySelector('#id') // ==> node ID
iframe.contentWindow.postMessage('sauceflexPictureInPictureOn','*') // pip  on
iframe.contentWindow.postMessage('sauceflexPictureInPictureOff','*') // pip  off
 
// * querySelector : https://developer.mozilla.org/ko/docs/Web/API/Document/querySelector

PIP Basic Scenario

You have uploaded a video of the PIP behavior. Please refer to it when developing it.

  1. [PIP mode is activated when pressing the home button during broadcast/vOD playback] (https://drive.google.com/file/d/1W7An4vlKiAkawpAL7AquXtzVNR6GSlVH/view?usp=sharing)
  2. [PIP mode is activated when push back button during broadcast/vOD playback] (https://drive.google.com/file/d/1Tsg96alzHuUlbErub8i6sjEm0NCsxLAp/view?usp=drive_link)
  3. [PIP mode is activated when you press the product sold during broadcast/vOD playback] (https://drive.google.com/file/d/1Ft2Y_bKdo5KgNWuRpJP74CBK1laxU-xX/view?usp=drive_link)