@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
44import android.content.Context
55import android.content.pm.PackageManager
66import android.content.res.Configuration
7+ import android.graphics.Rect
78import android.net.Uri
89import android.os.Build
910import android.os.Bundle
@@ -25,6 +26,10 @@ import android.widget.EditText
2526import android.widget.FrameLayout
2627import android.widget.TextView
2728import androidx.activity.ComponentActivity
29+ import java.util.concurrent.CountDownLatch
30+ import java.util.concurrent.TimeUnit
31+ import java.util.concurrent.atomic.AtomicBoolean
32+ import kotlin.math.roundToInt
2833
2934class MainActivity : ComponentActivity () {
3035
@@ -210,6 +215,7 @@ class MainActivity : ComponentActivity() {
210215 newConfig : Configuration
211216 ) {
212217 super .onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
218+ dispatchPictureInPictureChange(isInPictureInPictureMode)
213219 if (! isInPictureInPictureMode) {
214220 applyImmersiveMode()
215221 }
@@ -338,30 +344,82 @@ class MainActivity : ComponentActivity() {
338344 return packageManager.hasSystemFeature(PackageManager .FEATURE_PICTURE_IN_PICTURE )
339345 }
340346
347+ private fun dispatchPictureInPictureChange (isInPictureInPictureMode : Boolean ) {
348+ val js = """
349+ window.dispatchEvent(new CustomEvent('kvideo-android-pip-change', {
350+ detail: { inPictureInPicture: ${if (isInPictureInPictureMode) " true" else " false" } }
351+ }));
352+ """ .trimIndent()
353+
354+ webView.post {
355+ try {
356+ webView.evaluateJavascript(js, null )
357+ } catch (error: Throwable ) {
358+ Log .w(TAG , " Failed to dispatch Picture-in-Picture change event" , error)
359+ }
360+ }
361+ }
362+
363+ private fun createSourceRectHint (left : Int , top : Int , right : Int , bottom : Int ): Rect ? {
364+ if (right <= left || bottom <= top) {
365+ return null
366+ }
367+
368+ val density = resources.displayMetrics.density
369+ return Rect (
370+ (left * density).roundToInt(),
371+ (top * density).roundToInt(),
372+ (right * density).roundToInt(),
373+ (bottom * density).roundToInt()
374+ )
375+ }
376+
341377 private inner class AndroidPlayerBridge {
342378 @JavascriptInterface
343379 fun isPictureInPictureSupported (): Boolean = this @MainActivity.isPictureInPictureSupported()
344380
345381 @JavascriptInterface
346- fun enterPictureInPicture (width : Int , height : Int ): Boolean {
382+ fun enterPictureInPicture (
383+ width : Int ,
384+ height : Int ,
385+ left : Int ,
386+ top : Int ,
387+ right : Int ,
388+ bottom : Int
389+ ): Boolean {
347390 if (! this @MainActivity.isPictureInPictureSupported()) {
348391 return false
349392 }
350393
394+ val didEnterPiP = AtomicBoolean (false )
395+ val latch = CountDownLatch (1 )
396+
351397 runOnUiThread {
352398 try {
353- exitCustomFullscreen()
354399 val builder = android.app.PictureInPictureParams .Builder ()
355400 if (width > 0 && height > 0 ) {
356401 builder.setAspectRatio(Rational (width, height))
357402 }
358- enterPictureInPictureMode(builder.build())
403+ createSourceRectHint(left, top, right, bottom)?.let (builder::setSourceRectHint)
404+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .S ) {
405+ builder.setSeamlessResizeEnabled(true )
406+ }
407+ didEnterPiP.set(enterPictureInPictureMode(builder.build()))
359408 } catch (error: IllegalStateException ) {
360409 Log .w(TAG , " Failed to enter Picture-in-Picture mode" , error)
410+ } finally {
411+ latch.countDown()
361412 }
362413 }
363414
364- return true
415+ return try {
416+ latch.await(1500 , TimeUnit .MILLISECONDS )
417+ didEnterPiP.get()
418+ } catch (error: InterruptedException ) {
419+ Thread .currentThread().interrupt()
420+ Log .w(TAG , " Interrupted while waiting for Picture-in-Picture result" , error)
421+ false
422+ }
365423 }
366424 }
367425}
0 commit comments