2024-06-20 11:19:35 +02:00

220 lines
8.6 KiB
Kotlin

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.safeGetBool
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
import java.util.Objects
/**
* 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()
/** enable chunkless preparation for HLS
* see:
*/
var textTracksAllowChunklessPreparation: Boolean = false
override fun hashCode(): Int = Objects.hash(uriString, uri, startPositionMs, cropStartMs, cropEndMs, extension, metadata, headers)
/** 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 = 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"
private const val PROP_SRC_TEXT_TRACKS_ALLOW_CHUNKLESS_PREPARATION = "textTracksAllowChunklessPreparation"
@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)
source.textTracksAllowChunklessPreparation = safeGetBool(src, PROP_SRC_TEXT_TRACKS_ALLOW_CHUNKLESS_PREPARATION, true)
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"
)
}
}
}