Skip to content

Commit

Permalink
feat: pip mode refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
vnovick committed Aug 23, 2023
1 parent b69db8c commit 92e00d5
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.amazonaws.ivs.reactnative.player

import android.content.BroadcastReceiver
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.net.Uri
import android.widget.FrameLayout
Expand All @@ -17,20 +21,29 @@ import kotlin.concurrent.timerTask
import android.app.PictureInPictureParams
import android.app.Activity
import androidx.annotation.RequiresApi

import android.graphics.Rect
import android.view.View
import android.util.Rational
import android.app.RemoteAction
import android.graphics.drawable.Icon
private const val EXTRA_ACTION = "EXTRA_ACTION"
private const val ACTION_MEDIA_CONTROL = "pip_media_control"
private const val ACTION_PLAY = 0
private const val ACTION_PAUSE = ACTION_PLAY + 1

class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(context), LifecycleEventListener {

private var playerView: PlayerView? = null
private var player: Player? = null
private var streamUri: Uri? = null
private val playerListener: Player.Listener?

private var enabledBroadcast: Boolean = false
var playerObserver: Timer? = null
private var lastLiveLatency: Long? = null
private var lastBitrate: Long? = null
private var lastDuration: Long? = null
private var finishedLoading: Boolean = false

private val broadcastReceiver: BroadcastReceiver = buildBroadcastReceiver()
enum class Events(private val mName: String) {
STATE_CHANGED("onPlayerStateChange"),
DURATION_CHANGED("onDurationChange"),
Expand Down Expand Up @@ -104,6 +117,7 @@ class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(conte
playerObserver?.schedule(timerTask {
intervalHandler()
}, 0, 1000)
enableBroadcastReceiver()
}

override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
Expand All @@ -121,11 +135,37 @@ class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(conte

finishedLoading = false
player.load(uri)

reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(id, Events.LOAD_START.toString(), Arguments.createMap())
}
}

fun enableBroadcastReceiver(){
val activity: Activity? = context.currentActivity
if (enabledBroadcast) {
return
}

activity?.registerReceiver(
broadcastReceiver,
IntentFilter(ACTION_MEDIA_CONTROL)
)
enabledBroadcast = true
}

fun disableBroadcastReceiver(){
val activity: Activity? = context.currentActivity
if (!enabledBroadcast) {
return
}

try {
activity?.unregisterReceiver(broadcastReceiver)
} catch(ignore: IllegalArgumentException) {
enabledBroadcast = false;
}

}

fun setMuted(muted: Boolean) {
player?.isMuted = muted
}
Expand Down Expand Up @@ -303,7 +343,7 @@ class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(conte

fun onPlayerStateChange(state: Player.State) {
val reactContext = context as ReactContext

updatePipParams()
when (state) {
Player.State.PLAYING -> {
if (!finishedLoading) {
Expand Down Expand Up @@ -429,6 +469,99 @@ class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(conte
}
}

private fun getVideoAspectRatio(): Rational {
val width = 16
val height = 9
return Rational(playerView?.getWidth() ?: 16,playerView?.getHeight() ?: 9)
}

private fun getVisibleRectForView(view: View): Rect? {
val visibleRect = Rect()
if (view.getGlobalVisibleRect(visibleRect)) {
return visibleRect
}
return null
}

private fun buildBroadcastReceiver(): BroadcastReceiver {
val reactContext = context as ReactContext
return object : BroadcastReceiver() {
@RequiresApi(Build.VERSION_CODES.O)
override fun onReceive(context: Context?, intent: Intent?) {
intent?.getIntExtra(EXTRA_ACTION, -1)?.let { action ->
when (action) {
ACTION_PLAY -> play()
ACTION_PAUSE -> pause()
}
reactContext.currentActivity?.setPictureInPictureParams(getPipParams())
}
}
}
}



@RequiresApi(Build.VERSION_CODES.O)
fun buildPipActions(
playing: Boolean,
): List<RemoteAction> {
return mutableListOf<RemoteAction>().apply {
add(
if (playing) {
buildRemoteAction(
ACTION_PAUSE,
R.drawable.ic_pause,
"Pause",
"Pause Video"
)
} else {
buildRemoteAction(
ACTION_PLAY,
R.drawable.ic_play,
"Play",
"Play Video"
)
}
)
}
}



@RequiresApi(Build.VERSION_CODES.O)
fun getPipParams(): PictureInPictureParams {


val visibleRect = getVisibleRectForView(this)
val aspectRatio = getVideoAspectRatio()
val initialWidth = ((playerView?.getMeasuredWidth() ?: 0) * 0.75).toInt()
val initialHeight = ((playerView?.getMeasuredHeight() ?: 0) * 0.75).toInt()
val initialBonds = Rect(0, 0, initialWidth, initialHeight)

return PictureInPictureParams.Builder()
.setSourceRectHint(initialBonds)
.setActions(
buildPipActions(player?.getState() === Player.State.PLAYING)
)
.build()
}

@RequiresApi(Build.VERSION_CODES.O)
private fun buildRemoteAction(
requestId: Int,
iconId: Int,
title: String,
desc: String,
): RemoteAction {
val reactContext = context as ReactContext
val requestCode = requestId
val intent = Intent(ACTION_MEDIA_CONTROL).putExtra(EXTRA_ACTION, requestCode)
val pendingIntent =
PendingIntent.getBroadcast(reactContext, requestCode, intent, PendingIntent.FLAG_IMMUTABLE)
val icon: Icon = Icon.createWithResource(reactContext, iconId)
return RemoteAction(icon, title, desc, pendingIntent)
}



fun togglePip(){
Expand All @@ -438,15 +571,21 @@ class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(conte
PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
val activity: Activity? = context.currentActivity
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val params = PictureInPictureParams.Builder()
activity?.enterPictureInPictureMode(params.build());
activity?.enterPictureInPictureMode(getPipParams());
} else {
activity?.enterPictureInPictureMode();
}

}
}

private fun updatePipParams() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val activity: Activity? = context.currentActivity
activity?.setPictureInPictureParams(getPipParams())
}
}


override fun onHostResume() {
}
Expand All @@ -458,10 +597,10 @@ class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(conte
}

fun cleanup() {
disableBroadcastReceiver()
player?.removeListener(playerListener!!)
player?.release()
player = null

playerObserver?.cancel()
playerObserver = null
}
Expand Down
13 changes: 13 additions & 0 deletions android/src/main/res/drawable/ic_pause.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">

<path
android:fillColor="#000000"
android:pathData="M6 19h4V5H6v14zm8-14v14h4V5h-4z" />
<path
android:pathData="M0 0h24v24H0z" />
</vector>
13 changes: 13 additions & 0 deletions android/src/main/res/drawable/ic_play.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">

<path
android:fillColor="#000000"
android:pathData="M8 5v14l11-7z" />
<path
android:pathData="M0 0h24v24H0z" />
</vector>
3 changes: 3 additions & 0 deletions example/src/screens/AdvancedExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ export default function AdvancedExample() {
if (state === PlayerState.Playing || state === PlayerState.Idle) {
setBuffering(false);
}
if (state === PlayerState.Idle) {
setPaused(true);
}
}}
onProgress={(newPosition) => {
if (!lockPosition) {
Expand Down

0 comments on commit 92e00d5

Please sign in to comment.