Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,5 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "com.amazonaws:ivs-player:$ivs_version"
implementation("com.squareup.okhttp3:okhttp:5.2.1")
implementation "androidx.media:media:1.6.0"
}
8 changes: 7 additions & 1 deletion android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.amazonaws.ivs.reactnative.player">
package="com.amazonaws.ivs.reactnative.player">

<application>
<meta-data
android:name="com.amazonaws.ivs.player.FRAMEWORK_NAME"
android:value="reactnativeplayer" />
<meta-data
android:name="com.amazonaws.ivs.player.FRAMEWORK_VERSION"
android:value="${sdkVersion}" />

<service
android:name="com.amazonaws.ivs.reactnative.player.IVSBackgroundService"
android:exported="false"
android:foregroundServiceType="mediaPlayback" />
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
package com.amazonaws.ivs.reactnative.player

import android.Manifest
import android.app.Activity
import android.app.PictureInPictureParams
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.util.Log
import android.view.View
import android.widget.FrameLayout
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.amazonaws.ivs.player.Cue
import com.amazonaws.ivs.player.MediaPlayer
import com.amazonaws.ivs.player.Player
Expand Down Expand Up @@ -36,6 +44,7 @@ class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(conte
private var player: Player? = null
private var streamUri: Uri? = null
private val playerListener: Player.Listener?
private var controlReceiver: BroadcastReceiver? = null

var playerObserver: Timer? = null
private var lastLiveLatency: Long? = null
Expand All @@ -51,6 +60,10 @@ class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(conte
private var lastPosition: Long = 0

private var progressInterval: Long = 1000
private var wasPlayingBeforeBackground: Boolean = false
private var playInBackground: Boolean = false
private var notificationTitle: String = ""
private var notificationText: String = ""


private val eventDispatcher: EventDispatcher
Expand Down Expand Up @@ -86,6 +99,17 @@ class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(conte

context.addLifecycleEventListener(this)

playerView?.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
}

override fun onViewDetachedFromWindow(v: View) {
if (!playInBackground) {
playerView?.player?.pause()
}
}
})

playerListener = object : Player.Listener() {
override fun onStateChanged(state: Player.State) {
onPlayerStateChange(state)
Expand Down Expand Up @@ -124,6 +148,26 @@ class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(conte
}
}

controlReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val action = intent?.getStringExtra("action")
if (action == "pause") {
player?.pause()
if (playInBackground) startBackgroundService(false)
} else if (action == "play") {
player?.play()
if (playInBackground) startBackgroundService(true)
}
}
}

val filter = IntentFilter("IVS_PLAYER_CONTROL")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.registerReceiver(controlReceiver, filter, Context.RECEIVER_EXPORTED)
} else {
context.registerReceiver(controlReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
}

player?.addListener(playerListener);
addView(playerView)

Expand All @@ -140,11 +184,28 @@ class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(conte
}
}

private fun startBackgroundService(isPlaying: Boolean) {
val context = context.applicationContext
val serviceIntent = Intent(context, IVSBackgroundService::class.java)
serviceIntent.putExtra(IVSBackgroundService.EXTRA_IS_PLAYING, isPlaying)
serviceIntent.putExtra(IVSBackgroundService.NOTIFICATION_TITLE, this.notificationTitle)
serviceIntent.putExtra(IVSBackgroundService.NOTIFICATION_TEXT, this.notificationText)

ContextCompat.startForegroundService(context, serviceIntent)
}

private fun stopBackgroundService() {
val context = context.applicationContext
val serviceIntent = Intent(context, IVSBackgroundService::class.java)
context.stopService(serviceIntent)
}

fun setStreamUrl(streamUrl: String) {
player?.let { player ->
val reactContext = context as ReactContext
val uri = Uri.parse(streamUrl);
this.streamUri = uri;
playInBackground = true

finishedLoading = false
player.load(uri)
Expand All @@ -163,6 +224,29 @@ class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(conte
player?.isMuted = muted
}

fun setPlayInBackground(playInBackground: Boolean) {
if (playInBackground) {
checkAndRequestNotificationPermission()
}
this.playInBackground = playInBackground
}

fun setNotificationTitle(notificationTitle: String?) {
this.notificationTitle = notificationTitle ?: ""

if (isInBackground && playInBackground) {
startBackgroundService(player?.state == Player.State.PLAYING)
}
}

fun setNotificationText(notificationText: String?) {
this.notificationText = notificationText ?: ""

if (isInBackground && playInBackground) {
startBackgroundService(player?.state == Player.State.PLAYING)
}
}

fun setLooping(shouldLoop: Boolean) {
player?.setLooping(shouldLoop)
}
Expand Down Expand Up @@ -434,6 +518,7 @@ class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(conte

fun onPlayerStateChange(state: Player.State) {
val reactContext = context as ReactContext
this.keepScreenOn = (state == Player.State.PLAYING || state == Player.State.BUFFERING)

when (state) {
Player.State.PLAYING -> {
Expand Down Expand Up @@ -686,7 +771,7 @@ class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(conte
)
}

fun setProgressInterval(progressInterval: Int) {
fun setProgressInterval(progressInterval: Double) {
playerObserver?.cancel()
playerObserver?.purge()
playerObserver = Timer("observerInterval", false)
Expand All @@ -701,12 +786,30 @@ class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(conte

override fun onHostResume() {
isInBackground = false
stopBackgroundService()

if (wasPlayingBeforeBackground) {
player?.play()
wasPlayingBeforeBackground = false
}
}

override fun onHostPause() {
if (pipEnabled) {
isInBackground = true
togglePip()
} else {
if (playInBackground && player?.state == Player.State.PLAYING) {
startBackgroundService(true)
return
}

if (player?.state == Player.State.PLAYING || player?.state == Player.State.BUFFERING) {
wasPlayingBeforeBackground = true
player?.pause()
} else {
wasPlayingBeforeBackground = false
}
}
}

Expand All @@ -715,6 +818,7 @@ class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(conte
}

fun cleanup() {
stopBackgroundService()
// Cleanup any remaining sources
for (source in preloadSourceMap.values) {
source.release()
Expand All @@ -727,6 +831,11 @@ class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(conte

playerObserver?.cancel()
playerObserver = null

if (controlReceiver != null) {
context.unregisterReceiver(controlReceiver)
controlReceiver = null
}
}

fun TextCue.TextAlignment.toStringValue(): String {
Expand All @@ -737,4 +846,25 @@ class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(conte
}
}

private fun checkAndRequestNotificationPermission() {
if (Build.VERSION.SDK_INT >= 33) {
val reactContext = context as ReactContext
val activity = reactContext.currentActivity

if (ContextCompat.checkSelfPermission(
reactContext,
Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
activity?.let {
ActivityCompat.requestPermissions(
it,
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
101
)
}
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,32 @@ class AmazonIvsViewManager : SimpleViewManager<AmazonIvsView>(),

override fun setProgressInterval(
view: AmazonIvsView?,
value: Int
value: Double
) {
view?.setProgressInterval(value)
}

override fun setPlayInBackground(
view: AmazonIvsView?,
value: Boolean
) {
view?.setPlayInBackground(value)
}

override fun setNotificationTitle(
view: AmazonIvsView?,
value: String?
) {
view?.setNotificationTitle(value)
}

override fun setNotificationText(
view: AmazonIvsView?,
value: String?
) {
view?.setNotificationText(value)
}

override fun preload(
view: AmazonIvsView?,
url: String,
Expand Down
Loading
Loading