Skip to content
Open
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
5 changes: 2 additions & 3 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ buildscript {

repositories {
google()
jcenter()
mavenCentral()
}

dependencies {
Expand Down Expand Up @@ -52,7 +52,6 @@ android {

repositories {
mavenCentral()
jcenter()
google()

def found = false
Expand Down Expand Up @@ -128,5 +127,5 @@ dependencies {
api 'com.facebook.react:react-native:+'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"

implementation 'com.google.android.exoplayer:exoplayer:2.11.2'
implementation 'com.google.android.exoplayer:exoplayer:2.18.7'
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
package com.reactnativestandalonevideoplayer


import android.content.Context
import android.os.Looper
import android.util.Log
import android.view.SurfaceView
import com.facebook.react.uimanager.SimpleViewManager
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.annotations.ReactProp
import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
import com.google.android.exoplayer2.ui.PlayerView
import com.google.android.exoplayer2.ui.StyledPlayerView


class MyPlayerView(context: Context): PlayerView(context) {
class MyPlayerView(context: Context): StyledPlayerView(context) {

var playerInstance: Int = -1
var isBound: Boolean = false
Expand All @@ -25,65 +22,133 @@ class MyPlayerView(context: Context): PlayerView(context) {

class PlayerContainerView: SimpleViewManager<MyPlayerView>() {

companion object {
private const val TAG = "PlayerContainerView"

// Thread-safe pending views management
private val pendingViewsLock = Any()
private val _pendingViews: MutableList<MyPlayerView> = mutableListOf()

fun addPendingView(view: MyPlayerView) = synchronized(pendingViewsLock) {
if (!_pendingViews.contains(view)) {
_pendingViews.add(view)
}
}

fun removePendingView(view: MyPlayerView) = synchronized(pendingViewsLock) {
_pendingViews.remove(view)
}

fun clearPendingViews() = synchronized(pendingViewsLock) {
_pendingViews.clear()
}

fun bindPendingViews() = synchronized(pendingViewsLock) {
val iterator = _pendingViews.iterator()
while (iterator.hasNext()) {
val view = iterator.next()
val player = PlayerVideo.getInstance(view.playerInstance)
if (view.playerInstance >= 0 && player != null) {
val targetPlayer = if (view.isBound) player.player else null
view.player = targetPlayer
view.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL
(targetPlayer as? ExoPlayer)?.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT
iterator.remove()
}
}
}

fun hasPendingViews(): Boolean = synchronized(pendingViewsLock) {
_pendingViews.isNotEmpty()
}
}

init {
// this is created only once!
Log.d("PlayerView", "init PlayerContainerView")
Log.d(TAG, "init PlayerContainerView")
}

override fun getName() = "RNTPlayerVideoView"

@ReactProp(name = "isBoundToPlayer")
fun boundToPlayer(view: MyPlayerView, isBoundToPlayer: Boolean) {
Log.d("PlayerView", "boundToPlayer = ${isBoundToPlayer}")
Log.d(TAG, "boundToPlayer = $isBoundToPlayer")

view.isBound = isBoundToPlayer

setup(view)
if (view.isBound != isBoundToPlayer) {
view.isBound = isBoundToPlayer
setup(view)
}
}

@ReactProp(name = "playerInstance")
fun setPlayerInstance(view: MyPlayerView, instance: Int) {
Log.d("PlayerView", "playerInstance = ${instance}")
Log.d(TAG, "playerInstance = $instance")

view.playerInstance = instance

setup(view)
if (view.playerInstance != instance) {
view.playerInstance = instance
setup(view)
}
}

private fun setup(view: MyPlayerView) {
Log.d("PlayerView", "bind isBound=${view.isBound}, playerInstance=${view.playerInstance}")
Log.d("PlayerView", "view = ${view}")
Log.d(TAG, "setup isBound=${view.isBound}, playerInstance=${view.playerInstance}")

if (view.playerInstance < 0 || view.playerInstance >= PlayerVideo.instances.size) {
if (view.playerInstance < 0) {
return
}

view.player = if (view.isBound) PlayerVideo.instances[view.playerInstance].player else null
view.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL
(view.player as? SimpleExoPlayer)?.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING
// Wait for instance to be created if not yet available
val playerVideo = PlayerVideo.getInstance(view.playerInstance)
if (playerVideo == null) {
addPendingView(view)
return
}

// Remove from pending views if it was there
removePendingView(view)

val targetPlayer = if (view.isBound) playerVideo.player else null

//
// Always update player binding when isBound is true
if (view.isBound) {
// we have to setup again after videoSizeChanged otherwise video ratio would be wrong
PlayerVideo.instances[view.playerInstance].videoSizeChanged = { width, height ->
setup(view)
view.player = targetPlayer
view.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL
(targetPlayer as? ExoPlayer)?.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT

// Set up video size callback
playerVideo.videoSizeChanged = { _, _ ->
// Refresh resize mode when video size changes
view.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL
}
} else {
// Unbind player when not bound
view.player = null
}
}

//
// Try to bind any pending views now that we have more instances
if (hasPendingViews()) {
bindPendingViews()
}
}

override fun createViewInstance(reactContext: ThemedReactContext): MyPlayerView {
val playerView = MyPlayerView(reactContext)

playerView.useController = false
playerView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL
playerView.player = null
Log.d(TAG, "createViewInstance")

return MyPlayerView(reactContext).apply {
useController = false
resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL
player = null
// Disable shutter view for faster display
setShutterBackgroundColor(android.graphics.Color.TRANSPARENT)
}
}

Log.d("PlayerView", "createViewInstance")
override fun onDropViewInstance(view: MyPlayerView) {
Log.d(TAG, "onDropViewInstance")

return playerView
// Remove from pending views to prevent memory leak
removePendingView(view)
// Unbind player
view.player = null
super.onDropViewInstance(view)
}

}
Loading