Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(android): refactor source, fix random DRM issue and crop start on local asset #3835

Merged
merged 19 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
d3fc4fd
perf: ensure we do not provide callback to native if no callback prov…
freeboub May 5, 2024
e1da32d
chore: rework bufferConfig to make it more generic and reduce ReactEx…
freeboub May 6, 2024
df3da43
Merge branch 'master' of github.com:react-native-video/react-native-v…
freeboub May 8, 2024
0f33ad1
chore: improve issue template
freeboub May 8, 2024
17f2385
Merge branch 'TheWidlarzGroup:master' into master
freeboub May 10, 2024
1066898
fix(android): avoid video view flickering at playback startup
freeboub May 10, 2024
485f867
Merge branch 'master' of github.com:react-native-video/react-native-v…
freeboub May 11, 2024
a5e10a3
Merge branch 'master' of github.com:freeboub/react-native-video into …
freeboub May 11, 2024
9ce1d95
Merge branch 'master' of github.com:react-native-video/react-native-v…
freeboub May 15, 2024
fa27db7
Merge branch 'master' of github.com:react-native-video/react-native-v…
freeboub May 17, 2024
be681d3
Merge branch 'master' of github.com:react-native-video/react-native-v…
freeboub May 21, 2024
2fadb26
Merge branch 'master' of github.com:react-native-video/react-native-v…
freeboub May 23, 2024
92df10d
Merge branch 'master' of github.com:react-native-video/react-native-v…
freeboub May 24, 2024
bbb4133
fix: refactor source parameter parsing
freeboub May 24, 2024
2e06180
chore: override == instead of defining a specific function
freeboub May 24, 2024
8c3cb46
chore: fix previous commit
freeboub May 24, 2024
d280c52
Merge branch 'master' of github.com:react-native-video/react-native-v…
freeboub May 28, 2024
bf7ac9a
chore: restore metadata checks before appling them
freeboub May 28, 2024
38cbb9e
Merge branch 'master' of github.com:react-native-video/react-native-v…
freeboub May 28, 2024
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
210 changes: 210 additions & 0 deletions android/src/main/java/com/brentvatne/common/api/Source.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
package com.brentvatne.common.api

import android.annotation.SuppressLint
import android.content.ContentResolver
import android.content.Context
import android.content.res.Resources
import android.net.Uri
import android.text.TextUtils
import com.brentvatne.common.toolbox.DebugLog
import com.brentvatne.common.toolbox.DebugLog.e
import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetArray
import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetInt
import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetMap
import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetString
import com.facebook.react.bridge.ReadableMap
import java.util.Locale

/**
* Class representing Source props for host.
* Only generic code here, no reference to the player.
*/
class Source {
/** String value of source to playback */
private var uriString: String? = null

/** Parsed value of source to playback */
var uri: Uri? = null

/** Start position of playback used to resume playback */
var startPositionMs: Int = -1

/** Will crop content start at specified position */
var cropStartMs: Int = -1

/** Will crop content end at specified position */
var cropEndMs: Int = -1

/** Allow to force stream content, necessary when uri doesn't contain content type (.mlp4, .m3u, ...) */
var extension: String? = null

/** Metadata to display in notification */
var metadata: Metadata? = null

/** http header list */
val headers: MutableMap<String, String> = HashMap()

/** return true if this and src are equals */
override fun equals(other: Any?): Boolean {
if (other == null || other !is Source) return false
return (
uri == other.uri &&
cropStartMs == other.cropStartMs &&
cropEndMs == other.cropEndMs &&
startPositionMs == other.startPositionMs &&
extension == other.extension
)
}

/** return true if this and src are equals */
fun isEquals(source: Source): Boolean {
return this == source
}

/** Metadata to display in notification */
class Metadata {
/** Metadata title */
var title: String? = null

/** Metadata subtitle */
var subtitle: String? = null

/** Metadata description */
var description: String? = null

/** Metadata artist */
var artist: String? = null

/** image uri to display */
var imageUri: Uri? = null

companion object {
private const val PROP_SRC_METADATA_TITLE = "title"
private const val PROP_SRC_METADATA_SUBTITLE = "subtitle"
private const val PROP_SRC_METADATA_DESCRIPTION = "description"
private const val PROP_SRC_METADATA_ARTIST = "artist"
private const val PROP_SRC_METADATA_IMAGE_URI = "imageUri"

/** parse metadata object */
@JvmStatic
fun parse(src: ReadableMap?): Metadata? {
if (src != null) {
val metadata = Metadata()
metadata.title = safeGetString(src, PROP_SRC_METADATA_TITLE)
metadata.subtitle = safeGetString(src, PROP_SRC_METADATA_SUBTITLE)
metadata.description = safeGetString(src, PROP_SRC_METADATA_DESCRIPTION)
metadata.artist = safeGetString(src, PROP_SRC_METADATA_ARTIST)
val imageUriString = safeGetString(src, PROP_SRC_METADATA_IMAGE_URI)
try {
metadata.imageUri = Uri.parse(imageUriString)
} catch (e: Exception) {
e(TAG, "Could not parse imageUri in metadata")
}
return metadata
}
return null
}
}
}

companion object {
private const val TAG = "Source"
private const val PROP_SRC_URI = "uri"
private const val PROP_SRC_START_POSITION = "startPosition"
private const val PROP_SRC_CROP_START = "cropStart"
private const val PROP_SRC_CROP_END = "cropEnd"
private const val PROP_SRC_TYPE = "type"
private const val PROP_SRC_METADATA = "metadata"
private const val PROP_SRC_HEADERS = "requestHeaders"

@SuppressLint("DiscouragedApi")
private fun getUriFromAssetId(context: Context, uriString: String): Uri? {
val resources: Resources = context.resources
val packageName: String = context.packageName
var identifier = resources.getIdentifier(
uriString,
"drawable",
packageName
)
if (identifier == 0) {
identifier = resources.getIdentifier(
uriString,
"raw",
packageName
)
}

if (identifier <= 0) {
// cannot find identifier of content
DebugLog.d(TAG, "cannot find identifier")
return null
}
return Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE).path(identifier.toString()).build()
}

/** parse the source ReadableMap received from app */
@JvmStatic
fun parse(src: ReadableMap?, context: Context): Source {
val source = Source()

if (src != null) {
val uriString = safeGetString(src, PROP_SRC_URI, null)
if (uriString == null || TextUtils.isEmpty(uriString)) {
DebugLog.d(TAG, "isEmpty uri:$uriString")
return source
}
var uri = Uri.parse(uriString)
if (uri == null) {
// return an empty source
DebugLog.d(TAG, "Invalid uri:$uriString")
return source
} else if (!isValidScheme(uri.scheme)) {
uri = getUriFromAssetId(context, uriString)
if (uri == null) {
// cannot find identifier of content
DebugLog.d(TAG, "cannot find identifier")
return source
}
}
source.uriString = uriString
source.uri = uri
source.startPositionMs = safeGetInt(src, PROP_SRC_START_POSITION, -1)
source.cropStartMs = safeGetInt(src, PROP_SRC_CROP_START, -1)
source.cropEndMs = safeGetInt(src, PROP_SRC_CROP_END, -1)
source.extension = safeGetString(src, PROP_SRC_TYPE, null)

val propSrcHeadersArray = safeGetArray(src, PROP_SRC_HEADERS)
if (propSrcHeadersArray != null) {
if (propSrcHeadersArray.size() > 0) {
for (i in 0 until propSrcHeadersArray.size()) {
val current = propSrcHeadersArray.getMap(i)
val key = if (current.hasKey("key")) current.getString("key") else null
val value = if (current.hasKey("value")) current.getString("value") else null
if (key != null && value != null) {
source.headers[key] = value
}
}
}
}
source.metadata = Metadata.parse(safeGetMap(src, PROP_SRC_METADATA))
}
return source
}

/** return true if rui scheme is supported for android playback */
private fun isValidScheme(scheme: String?): Boolean {
if (scheme == null) {
return false
}
val lowerCaseUri = scheme.lowercase(Locale.getDefault())
return (
lowerCaseUri == "http" ||
lowerCaseUri == "https" ||
lowerCaseUri == "content" ||
lowerCaseUri == "file" ||
lowerCaseUri == "rtsp" ||
lowerCaseUri == "asset"
)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.brentvatne.exoplayer

import androidx.media3.common.MediaItem.LiveConfiguration
import androidx.media3.common.MediaMetadata
import com.brentvatne.common.api.BufferConfig
import com.brentvatne.common.api.BufferConfig.Live
import com.brentvatne.common.api.Source

/**
* Helper functions to create exoplayer configuration
Expand Down Expand Up @@ -33,4 +35,22 @@ object ConfigurationUtils {
}
return liveConfiguration
}

/**
* Generate exoplayer MediaMetadata from source.Metadata
*/
@JvmStatic
fun buildCustomMetadata(metadata: Source.Metadata?): MediaMetadata? {
var customMetadata: MediaMetadata? = null
if (metadata != null) {
customMetadata = MediaMetadata.Builder()
.setTitle(metadata.title)
.setSubtitle(metadata.subtitle)
.setDescription(metadata.description)
.setArtist(metadata.artist)
.setArtworkUri(metadata.imageUri)
.build()
}
return customMetadata
}
}
Loading
Loading