2021-07-19 13:19:47 +00:00
|
|
|
package com.lagradost.cloudstream3.utils
|
|
|
|
|
2022-02-13 00:53:40 +00:00
|
|
|
import android.annotation.SuppressLint
|
2021-07-30 01:14:53 +00:00
|
|
|
import android.app.Activity
|
2021-07-19 13:19:47 +00:00
|
|
|
import android.content.ContentValues
|
|
|
|
import android.content.Context
|
2021-11-07 22:10:19 +00:00
|
|
|
import android.content.Intent
|
2021-07-30 01:14:53 +00:00
|
|
|
import android.content.pm.PackageManager
|
2022-01-07 19:27:25 +00:00
|
|
|
import android.database.Cursor
|
2021-07-30 01:14:53 +00:00
|
|
|
import android.media.AudioAttributes
|
|
|
|
import android.media.AudioFocusRequest
|
|
|
|
import android.media.AudioManager
|
2022-02-13 00:53:40 +00:00
|
|
|
import android.media.tv.TvContract.Channels.COLUMN_INTERNAL_PROVIDER_ID
|
2021-07-30 01:14:53 +00:00
|
|
|
import android.net.ConnectivityManager
|
|
|
|
import android.net.NetworkCapabilities
|
2021-07-19 13:19:47 +00:00
|
|
|
import android.net.Uri
|
2021-07-30 01:14:53 +00:00
|
|
|
import android.os.Build
|
2022-01-07 19:27:25 +00:00
|
|
|
import android.os.Environment
|
|
|
|
import android.os.ParcelFileDescriptor
|
2021-07-19 13:19:47 +00:00
|
|
|
import android.provider.MediaStore
|
2022-06-29 01:20:23 +00:00
|
|
|
import android.text.Spanned
|
2022-01-07 19:27:25 +00:00
|
|
|
import android.util.Log
|
2022-02-13 00:53:40 +00:00
|
|
|
import androidx.annotation.RequiresApi
|
|
|
|
import androidx.annotation.WorkerThread
|
2021-07-30 01:14:53 +00:00
|
|
|
import androidx.appcompat.app.AppCompatActivity
|
2022-04-01 20:05:34 +00:00
|
|
|
import androidx.core.content.ContextCompat
|
2022-06-29 01:20:23 +00:00
|
|
|
import androidx.core.text.HtmlCompat
|
|
|
|
import androidx.core.text.toSpanned
|
2022-08-02 16:26:16 +00:00
|
|
|
import androidx.recyclerview.widget.LinearLayoutManager
|
2022-07-30 15:30:23 +00:00
|
|
|
import androidx.recyclerview.widget.RecyclerView
|
2022-02-13 00:53:40 +00:00
|
|
|
import androidx.tvprovider.media.tv.PreviewChannelHelper
|
|
|
|
import androidx.tvprovider.media.tv.TvContractCompat
|
|
|
|
import androidx.tvprovider.media.tv.WatchNextProgram
|
|
|
|
import androidx.tvprovider.media.tv.WatchNextProgram.fromCursor
|
2022-01-14 18:14:24 +00:00
|
|
|
import com.fasterxml.jackson.module.kotlin.readValue
|
2021-07-30 01:14:53 +00:00
|
|
|
import com.google.android.gms.cast.framework.CastContext
|
|
|
|
import com.google.android.gms.cast.framework.CastState
|
|
|
|
import com.google.android.gms.common.ConnectionResult
|
|
|
|
import com.google.android.gms.common.GoogleApiAvailability
|
|
|
|
import com.google.android.gms.common.wrappers.Wrappers
|
2022-04-01 20:05:34 +00:00
|
|
|
import com.lagradost.cloudstream3.R
|
|
|
|
import com.lagradost.cloudstream3.SearchResponse
|
|
|
|
import com.lagradost.cloudstream3.isMovieType
|
|
|
|
import com.lagradost.cloudstream3.mapper
|
2021-12-16 23:45:20 +00:00
|
|
|
import com.lagradost.cloudstream3.mvvm.logError
|
2021-07-30 01:14:53 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.result.ResultFragment
|
2022-02-13 00:53:40 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
2022-01-07 19:27:25 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.FillerEpisodeCheck.toClassDir
|
|
|
|
import com.lagradost.cloudstream3.utils.JsUnpacker.Companion.load
|
2021-09-20 21:11:36 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
2022-01-07 19:27:25 +00:00
|
|
|
import okhttp3.Cache
|
|
|
|
import java.io.*
|
2021-11-07 22:10:19 +00:00
|
|
|
import java.net.URL
|
|
|
|
import java.net.URLDecoder
|
2021-07-19 13:19:47 +00:00
|
|
|
|
|
|
|
object AppUtils {
|
2022-07-30 15:30:23 +00:00
|
|
|
fun RecyclerView.setMaxViewPoolSize(maxViewTypeId: Int, maxPoolSize: Int) {
|
|
|
|
for (i in 0..maxViewTypeId)
|
|
|
|
recycledViewPool.setMaxRecycledViews(i, maxPoolSize)
|
|
|
|
}
|
|
|
|
|
2022-08-02 16:26:16 +00:00
|
|
|
fun RecyclerView.isRecyclerScrollable(): Boolean {
|
|
|
|
val layoutManager =
|
|
|
|
this.layoutManager as? LinearLayoutManager?
|
|
|
|
val adapter = adapter
|
|
|
|
return if (layoutManager == null || adapter == null) false else layoutManager.findLastCompletelyVisibleItemPosition() < adapter.itemCount - 2
|
|
|
|
}
|
|
|
|
|
2022-02-13 00:53:40 +00:00
|
|
|
//fun Context.deleteFavorite(data: SearchResponse) {
|
|
|
|
// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
|
|
|
// normalSafeApiCall {
|
|
|
|
// val existingId =
|
|
|
|
// getWatchNextProgramByVideoId(data.url, this).second ?: return@normalSafeApiCall
|
|
|
|
// contentResolver.delete(
|
|
|
|
//
|
|
|
|
// TvContractCompat.buildWatchNextProgramUri(existingId),
|
|
|
|
// null, null
|
|
|
|
// )
|
|
|
|
// }
|
|
|
|
//}
|
2022-06-29 01:20:23 +00:00
|
|
|
fun String?.html(): Spanned {
|
|
|
|
return getHtmlText(this ?: return "".toSpanned())
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun getHtmlText(text: String): Spanned {
|
|
|
|
return try {
|
|
|
|
// I have no idea if this can throw any error, but I dont want to try
|
|
|
|
HtmlCompat.fromHtml(
|
|
|
|
text, HtmlCompat.FROM_HTML_MODE_LEGACY
|
|
|
|
)
|
|
|
|
} catch (e: Exception) {
|
|
|
|
logError(e)
|
|
|
|
text.toSpanned()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-13 00:53:40 +00:00
|
|
|
@SuppressLint("RestrictedApi")
|
|
|
|
private fun buildWatchNextProgramUri(
|
|
|
|
context: Context,
|
|
|
|
card: DataStoreHelper.ResumeWatchingResult
|
|
|
|
): WatchNextProgram {
|
2022-03-20 23:59:17 +00:00
|
|
|
val isSeries = card.type?.isMovieType() == false
|
2022-02-13 00:53:40 +00:00
|
|
|
val title = if (isSeries) {
|
|
|
|
context.getNameFull(card.name, card.episode, card.season)
|
|
|
|
} else {
|
|
|
|
card.name
|
|
|
|
}
|
|
|
|
|
|
|
|
val builder = WatchNextProgram.Builder()
|
|
|
|
.setEpisodeTitle(title)
|
|
|
|
.setType(
|
|
|
|
if (isSeries) {
|
|
|
|
TvContractCompat.WatchNextPrograms.TYPE_TV_EPISODE
|
|
|
|
} else TvContractCompat.WatchNextPrograms.TYPE_MOVIE
|
|
|
|
)
|
|
|
|
.setWatchNextType(TvContractCompat.WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE)
|
|
|
|
.setTitle(title)
|
|
|
|
.setPosterArtUri(Uri.parse(card.posterUrl))
|
|
|
|
.setIntentUri(Uri.parse(card.url)) //TODO FIX intent
|
|
|
|
.setInternalProviderId(card.url)
|
|
|
|
//.setLastEngagementTimeUtcMillis(System.currentTimeMillis())
|
|
|
|
|
|
|
|
card.watchPos?.let {
|
|
|
|
builder.setDurationMillis(it.duration.toInt())
|
|
|
|
builder.setLastPlaybackPositionMillis(it.position.toInt())
|
|
|
|
}
|
|
|
|
// .setLastEngagementTimeUtcMillis() //TODO
|
|
|
|
|
|
|
|
if (isSeries)
|
|
|
|
card.episode?.let {
|
|
|
|
builder.setEpisodeNumber(it)
|
|
|
|
}
|
|
|
|
|
|
|
|
return builder.build()
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find the Watch Next program for given id.
|
|
|
|
* Returns the first instance available.
|
|
|
|
*/
|
|
|
|
@SuppressLint("RestrictedApi")
|
|
|
|
// Suppress RestrictedApi due to https://issuetracker.google.com/138150076
|
|
|
|
fun findFirstWatchNextProgram(context: Context, predicate: (Cursor) -> Boolean):
|
|
|
|
Pair<WatchNextProgram?, Long?> {
|
|
|
|
val COLUMN_WATCH_NEXT_ID_INDEX = 0
|
|
|
|
// val COLUMN_WATCH_NEXT_INTERNAL_PROVIDER_ID_INDEX = 1
|
|
|
|
// val COLUMN_WATCH_NEXT_COLUMN_BROWSABLE_INDEX = 2
|
|
|
|
|
|
|
|
val cursor = context.contentResolver.query(
|
|
|
|
TvContractCompat.WatchNextPrograms.CONTENT_URI,
|
|
|
|
WatchNextProgram.PROJECTION,
|
|
|
|
/* selection = */ null,
|
|
|
|
/* selectionArgs = */ null,
|
|
|
|
/* sortOrder= */ null
|
|
|
|
)
|
|
|
|
cursor?.use {
|
|
|
|
if (it.moveToFirst()) {
|
|
|
|
do {
|
|
|
|
if (predicate(cursor)) {
|
|
|
|
return fromCursor(cursor) to cursor.getLong(COLUMN_WATCH_NEXT_ID_INDEX)
|
|
|
|
}
|
|
|
|
} while (it.moveToNext())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null to null
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Query the Watch Next list and find the program with given videoId.
|
|
|
|
* Return null if not found.
|
|
|
|
*/
|
|
|
|
|
|
|
|
@RequiresApi(Build.VERSION_CODES.O)
|
|
|
|
@SuppressLint("Range")
|
|
|
|
@Synchronized
|
|
|
|
private fun getWatchNextProgramByVideoId(
|
|
|
|
id: String,
|
|
|
|
context: Context
|
|
|
|
): Pair<WatchNextProgram?, Long?> {
|
|
|
|
return findFirstWatchNextProgram(context) { cursor ->
|
|
|
|
(cursor.getString(cursor.getColumnIndex(COLUMN_INTERNAL_PROVIDER_ID)) == id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// https://github.com/googlearchive/leanback-homescreen-channels/blob/master/app/src/main/java/com/google/android/tvhomescreenchannels/SampleTvProvider.java
|
|
|
|
@SuppressLint("RestrictedApi")
|
|
|
|
@WorkerThread
|
|
|
|
fun Context.addProgramsToContinueWatching(data: List<DataStoreHelper.ResumeWatchingResult>) {
|
|
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
2022-08-03 00:04:03 +00:00
|
|
|
val context = this
|
2022-02-13 00:53:40 +00:00
|
|
|
ioSafe {
|
|
|
|
data.forEach { episodeInfo ->
|
|
|
|
try {
|
2022-08-03 00:04:03 +00:00
|
|
|
val (program, id) = getWatchNextProgramByVideoId(episodeInfo.url, context)
|
|
|
|
val nextProgram = buildWatchNextProgramUri(context, episodeInfo)
|
2022-02-13 00:53:40 +00:00
|
|
|
|
|
|
|
// If the program is already in the Watch Next row, update it
|
|
|
|
if (program != null && id != null) {
|
2022-08-03 00:04:03 +00:00
|
|
|
PreviewChannelHelper(context).updateWatchNextProgram(
|
2022-02-13 00:53:40 +00:00
|
|
|
nextProgram,
|
|
|
|
id,
|
|
|
|
)
|
|
|
|
} else {
|
2022-08-03 00:04:03 +00:00
|
|
|
PreviewChannelHelper(context)
|
2022-02-13 00:53:40 +00:00
|
|
|
.publishWatchNextProgram(nextProgram)
|
|
|
|
}
|
|
|
|
} catch (e: Exception) {
|
|
|
|
logError(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@SuppressLint("Range")
|
2021-07-19 13:19:47 +00:00
|
|
|
fun getVideoContentUri(context: Context, videoFilePath: String): Uri? {
|
|
|
|
val cursor = context.contentResolver.query(
|
|
|
|
MediaStore.Video.Media.EXTERNAL_CONTENT_URI, arrayOf(MediaStore.Video.Media._ID),
|
|
|
|
MediaStore.Video.Media.DATA + "=? ", arrayOf(videoFilePath), null
|
|
|
|
)
|
|
|
|
return if (cursor != null && cursor.moveToFirst()) {
|
|
|
|
val id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID))
|
|
|
|
cursor.close()
|
|
|
|
Uri.withAppendedPath(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "" + id)
|
|
|
|
} else {
|
|
|
|
val values = ContentValues()
|
|
|
|
values.put(MediaStore.Video.Media.DATA, videoFilePath)
|
|
|
|
context.contentResolver.insert(
|
|
|
|
MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2021-07-30 01:14:53 +00:00
|
|
|
|
2021-11-07 22:10:19 +00:00
|
|
|
fun Context.openBrowser(url: String) {
|
2021-12-16 23:45:20 +00:00
|
|
|
try {
|
|
|
|
val intent = Intent(Intent.ACTION_VIEW)
|
|
|
|
intent.data = Uri.parse(url)
|
2022-04-01 20:05:34 +00:00
|
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
|
|
|
ContextCompat.startActivity(this, intent, null)
|
2022-01-07 19:27:25 +00:00
|
|
|
} catch (e: Exception) {
|
2021-12-16 23:45:20 +00:00
|
|
|
logError(e)
|
|
|
|
}
|
2021-11-07 22:10:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fun splitQuery(url: URL): Map<String, String> {
|
|
|
|
val queryPairs: MutableMap<String, String> = LinkedHashMap()
|
|
|
|
val query: String = url.query
|
|
|
|
val pairs = query.split("&").toTypedArray()
|
|
|
|
for (pair in pairs) {
|
|
|
|
val idx = pair.indexOf("=")
|
|
|
|
queryPairs[URLDecoder.decode(pair.substring(0, idx), "UTF-8")] =
|
|
|
|
URLDecoder.decode(pair.substring(idx + 1), "UTF-8")
|
|
|
|
}
|
|
|
|
return queryPairs
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:10:19 +00:00
|
|
|
/** Any object as json string */
|
|
|
|
fun Any.toJson(): String {
|
2022-06-29 01:20:23 +00:00
|
|
|
if (this is String) return this
|
2021-11-28 16:10:19 +00:00
|
|
|
return mapper.writeValueAsString(this)
|
|
|
|
}
|
|
|
|
|
2022-02-13 00:53:40 +00:00
|
|
|
inline fun <reified T> parseJson(value: String): T {
|
2022-01-14 18:14:24 +00:00
|
|
|
return mapper.readValue(value)
|
|
|
|
}
|
|
|
|
|
2022-04-02 19:01:00 +00:00
|
|
|
inline fun <reified T> tryParseJson(value: String?): T? {
|
2022-01-16 22:31:42 +00:00
|
|
|
return try {
|
2022-04-02 19:01:00 +00:00
|
|
|
parseJson(value ?: return null)
|
2022-02-13 00:53:40 +00:00
|
|
|
} catch (_: Exception) {
|
2022-01-16 22:31:42 +00:00
|
|
|
null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-09 14:40:20 +00:00
|
|
|
/**| S1:E2 Hello World
|
|
|
|
* | Episode 2. Hello world
|
|
|
|
* | Hello World
|
|
|
|
* | Season 1 - Episode 2
|
|
|
|
* | Episode 2
|
|
|
|
* **/
|
2021-09-02 13:19:50 +00:00
|
|
|
fun Context.getNameFull(name: String?, episode: Int?, season: Int?): String {
|
2021-08-25 15:28:25 +00:00
|
|
|
val rEpisode = if (episode == 0) null else episode
|
|
|
|
val rSeason = if (season == 0) null else season
|
2021-08-11 18:26:19 +00:00
|
|
|
|
2021-09-02 13:19:50 +00:00
|
|
|
val seasonName = getString(R.string.season)
|
|
|
|
val episodeName = getString(R.string.episode)
|
|
|
|
val seasonNameShort = getString(R.string.season_short)
|
|
|
|
val episodeNameShort = getString(R.string.episode_short)
|
|
|
|
|
2021-08-09 14:40:20 +00:00
|
|
|
if (name != null) {
|
2021-08-25 15:28:25 +00:00
|
|
|
return if (rEpisode != null && rSeason != null) {
|
2021-09-02 13:19:50 +00:00
|
|
|
"$seasonNameShort${rSeason}:$episodeNameShort${rEpisode} $name"
|
2021-08-25 15:28:25 +00:00
|
|
|
} else if (rEpisode != null) {
|
2021-09-02 13:19:50 +00:00
|
|
|
"$episodeName $rEpisode. $name"
|
2021-08-09 14:40:20 +00:00
|
|
|
} else {
|
|
|
|
name
|
|
|
|
}
|
|
|
|
} else {
|
2021-08-25 15:28:25 +00:00
|
|
|
if (rEpisode != null && rSeason != null) {
|
2021-09-02 13:19:50 +00:00
|
|
|
return "$seasonName $rSeason - $episodeName $rEpisode"
|
2021-08-25 15:28:25 +00:00
|
|
|
} else if (rSeason == null) {
|
2021-09-02 13:19:50 +00:00
|
|
|
return "$episodeName $rEpisode"
|
2021-08-09 14:40:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2022-01-07 19:27:25 +00:00
|
|
|
fun Activity?.loadCache() {
|
|
|
|
try {
|
|
|
|
cacheClass("android.net.NetworkCapabilities".load())
|
|
|
|
} catch (_: Exception) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-05 21:39:56 +00:00
|
|
|
//private val viewModel: ResultViewModel by activityViewModels()
|
|
|
|
|
2021-11-28 16:10:19 +00:00
|
|
|
fun AppCompatActivity.loadResult(
|
|
|
|
url: String,
|
|
|
|
apiName: String,
|
|
|
|
startAction: Int = 0,
|
|
|
|
startValue: Int = 0
|
|
|
|
) {
|
2021-07-30 01:14:53 +00:00
|
|
|
this.runOnUiThread {
|
2021-11-07 22:10:19 +00:00
|
|
|
// viewModelStore.clear()
|
|
|
|
this.navigate(
|
|
|
|
R.id.global_to_navigation_results,
|
|
|
|
ResultFragment.newInstance(url, apiName, startAction, startValue)
|
|
|
|
)
|
2021-07-30 01:14:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:10:19 +00:00
|
|
|
fun Activity?.loadSearchResult(
|
|
|
|
card: SearchResponse,
|
|
|
|
startAction: Int = 0,
|
2022-05-02 21:32:28 +00:00
|
|
|
startValue: Int? = null,
|
2021-11-28 16:10:19 +00:00
|
|
|
) {
|
2022-05-02 21:32:28 +00:00
|
|
|
this?.runOnUiThread {
|
|
|
|
// viewModelStore.clear()
|
|
|
|
this.navigate(
|
|
|
|
R.id.global_to_navigation_results,
|
|
|
|
ResultFragment.newInstance(card, startAction, startValue)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
//(this as? AppCompatActivity?)?.loadResult(card.url, card.apiName, startAction, startValue)
|
2021-07-30 01:14:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fun Activity.requestLocalAudioFocus(focusRequest: AudioFocusRequest?) {
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && focusRequest != null) {
|
|
|
|
val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
|
|
audioManager.requestAudioFocus(focusRequest)
|
|
|
|
} else {
|
|
|
|
val audioManager: AudioManager =
|
|
|
|
getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
|
|
audioManager.requestAudioFocus(
|
|
|
|
null,
|
|
|
|
AudioManager.STREAM_MUSIC,
|
|
|
|
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private var currentAudioFocusRequest: AudioFocusRequest? = null
|
|
|
|
private var currentAudioFocusChangeListener: AudioManager.OnAudioFocusChangeListener? = null
|
|
|
|
var onAudioFocusEvent = Event<Boolean>()
|
|
|
|
|
|
|
|
private fun getAudioListener(): AudioManager.OnAudioFocusChangeListener? {
|
|
|
|
if (currentAudioFocusChangeListener != null) return currentAudioFocusChangeListener
|
|
|
|
currentAudioFocusChangeListener = AudioManager.OnAudioFocusChangeListener {
|
|
|
|
onAudioFocusEvent.invoke(
|
|
|
|
when (it) {
|
|
|
|
AudioManager.AUDIOFOCUS_GAIN -> false
|
|
|
|
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE -> false
|
|
|
|
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT -> false
|
|
|
|
else -> true
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return currentAudioFocusChangeListener
|
|
|
|
}
|
|
|
|
|
|
|
|
fun Context.isCastApiAvailable(): Boolean {
|
|
|
|
val isCastApiAvailable =
|
|
|
|
GoogleApiAvailability.getInstance()
|
|
|
|
.isGooglePlayServicesAvailable(applicationContext) == ConnectionResult.SUCCESS
|
|
|
|
try {
|
|
|
|
applicationContext?.let { CastContext.getSharedInstance(it) }
|
|
|
|
} catch (e: Exception) {
|
|
|
|
println(e)
|
|
|
|
// track non-fatal
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return isCastApiAvailable
|
|
|
|
}
|
|
|
|
|
|
|
|
fun Context.isConnectedToChromecast(): Boolean {
|
|
|
|
if (isCastApiAvailable()) {
|
|
|
|
val castContext = CastContext.getSharedInstance(this)
|
|
|
|
if (castContext.castState == CastState.CONNECTED) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2022-01-07 19:27:25 +00:00
|
|
|
// Copied from https://github.com/videolan/vlc-android/blob/master/application/vlc-android/src/org/videolan/vlc/util/FileUtils.kt
|
2022-02-13 00:53:40 +00:00
|
|
|
@SuppressLint("Range")
|
2022-01-07 19:27:25 +00:00
|
|
|
fun Context.getUri(data: Uri?): Uri? {
|
|
|
|
var uri = data
|
|
|
|
val ctx = this
|
|
|
|
if (data != null && data.scheme == "content") {
|
|
|
|
// Mail-based apps - download the stream to a temporary file and play it
|
|
|
|
if ("com.fsck.k9.attachmentprovider" == data.host || "gmail-ls" == data.host) {
|
|
|
|
var inputStream: InputStream? = null
|
|
|
|
var os: OutputStream? = null
|
|
|
|
var cursor: Cursor? = null
|
|
|
|
try {
|
|
|
|
cursor = ctx.contentResolver.query(
|
|
|
|
data,
|
|
|
|
arrayOf(MediaStore.MediaColumns.DISPLAY_NAME), null, null, null
|
|
|
|
)
|
|
|
|
if (cursor != null && cursor.moveToFirst()) {
|
2022-02-13 00:53:40 +00:00
|
|
|
val filename =
|
|
|
|
cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME))
|
|
|
|
.replace("/", "")
|
2022-01-07 19:27:25 +00:00
|
|
|
inputStream = ctx.contentResolver.openInputStream(data)
|
|
|
|
if (inputStream == null) return data
|
2022-02-13 00:53:40 +00:00
|
|
|
os =
|
|
|
|
FileOutputStream(Environment.getExternalStorageDirectory().path + "/Download/" + filename)
|
2022-01-07 19:27:25 +00:00
|
|
|
val buffer = ByteArray(1024)
|
|
|
|
var bytesRead = inputStream.read(buffer)
|
|
|
|
while (bytesRead >= 0) {
|
|
|
|
os.write(buffer, 0, bytesRead)
|
|
|
|
bytesRead = inputStream.read(buffer)
|
|
|
|
}
|
|
|
|
uri =
|
|
|
|
Uri.fromFile(File(Environment.getExternalStorageDirectory().path + "/Download/" + filename))
|
|
|
|
}
|
|
|
|
} catch (e: Exception) {
|
|
|
|
return null
|
|
|
|
} finally {
|
|
|
|
inputStream?.close()
|
|
|
|
os?.close()
|
|
|
|
cursor?.close()
|
|
|
|
}
|
|
|
|
} else if (data.authority == "media") {
|
|
|
|
uri = this.contentResolver.query(
|
|
|
|
data,
|
|
|
|
arrayOf(MediaStore.Video.Media.DATA), null, null, null
|
|
|
|
)?.use {
|
|
|
|
val columnIndex = it.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)
|
2022-02-13 00:53:40 +00:00
|
|
|
if (it.moveToFirst()) Uri.fromFile(File(it.getString(columnIndex)))
|
|
|
|
?: data else data
|
2022-01-07 19:27:25 +00:00
|
|
|
}
|
|
|
|
//uri = MediaUtils.getContentMediaUri(data)
|
|
|
|
/*} else if (data.authority == ctx.getString(R.string.tv_provider_authority)) {
|
|
|
|
println("TV AUTHORITY")
|
|
|
|
//val medialibrary = Medialibrary.getInstance()
|
|
|
|
//val media = medialibrary.getMedia(data.lastPathSegment!!.toLong())
|
|
|
|
uri = null//media.uri*/
|
|
|
|
} else {
|
|
|
|
val inputPFD: ParcelFileDescriptor?
|
|
|
|
try {
|
|
|
|
inputPFD = ctx.contentResolver.openFileDescriptor(data, "r")
|
|
|
|
if (inputPFD == null) return data
|
|
|
|
uri = Uri.parse("fd://" + inputPFD.fd)
|
|
|
|
// Cursor returnCursor =
|
|
|
|
// getContentResolver().query(data, null, null, null, null);
|
|
|
|
// if (returnCursor != null) {
|
|
|
|
// if (returnCursor.getCount() > 0) {
|
|
|
|
// int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
|
|
|
|
// if (nameIndex > -1) {
|
|
|
|
// returnCursor.moveToFirst();
|
|
|
|
// title = returnCursor.getString(nameIndex);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// returnCursor.close();
|
|
|
|
// }
|
|
|
|
} catch (e: FileNotFoundException) {
|
|
|
|
Log.e("TAG", "${e.message} for $data", e)
|
|
|
|
return null
|
|
|
|
} catch (e: IllegalArgumentException) {
|
|
|
|
Log.e("TAG", "${e.message} for $data", e)
|
|
|
|
return null
|
|
|
|
} catch (e: IllegalStateException) {
|
|
|
|
Log.e("TAG", "${e.message} for $data", e)
|
|
|
|
return null
|
|
|
|
} catch (e: NullPointerException) {
|
|
|
|
Log.e("TAG", "${e.message} for $data", e)
|
|
|
|
return null
|
|
|
|
} catch (e: SecurityException) {
|
|
|
|
Log.e("TAG", "${e.message} for $data", e)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
}// Media or MMS URI
|
|
|
|
}
|
|
|
|
return uri
|
|
|
|
}
|
|
|
|
|
2021-07-30 01:14:53 +00:00
|
|
|
fun Context.isUsingMobileData(): Boolean {
|
|
|
|
val conManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
|
|
|
val networkInfo = conManager.allNetworks
|
|
|
|
return networkInfo.any {
|
2021-11-28 16:10:19 +00:00
|
|
|
conManager.getNetworkCapabilities(it)
|
|
|
|
?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true
|
2021-07-30 01:14:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-07 19:27:25 +00:00
|
|
|
private fun Activity?.cacheClass(clazz: String?) {
|
|
|
|
clazz?.let { c ->
|
|
|
|
this?.cacheDir?.let {
|
|
|
|
Cache(
|
|
|
|
directory = File(it, c.toClassDir()),
|
|
|
|
maxSize = 20L * 1024L * 1024L // 20 MiB
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-30 01:14:53 +00:00
|
|
|
fun Context.isAppInstalled(uri: String): Boolean {
|
|
|
|
val pm = Wrappers.packageManager(this)
|
2022-05-05 21:50:08 +00:00
|
|
|
|
|
|
|
return try {
|
|
|
|
pm.getPackageInfo(uri, 0) // PackageManager.GET_ACTIVITIES
|
2021-07-30 01:14:53 +00:00
|
|
|
true
|
|
|
|
} catch (e: PackageManager.NameNotFoundException) {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun getFocusRequest(): AudioFocusRequest? {
|
|
|
|
if (currentAudioFocusRequest != null) return currentAudioFocusRequest
|
|
|
|
currentAudioFocusRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
|
|
AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).run {
|
|
|
|
setAudioAttributes(AudioAttributes.Builder().run {
|
|
|
|
setUsage(AudioAttributes.USAGE_MEDIA)
|
|
|
|
setContentType(AudioAttributes.CONTENT_TYPE_MOVIE)
|
|
|
|
build()
|
|
|
|
})
|
|
|
|
setAcceptsDelayedFocusGain(true)
|
|
|
|
getAudioListener()?.let {
|
|
|
|
setOnAudioFocusChangeListener(it)
|
|
|
|
}
|
|
|
|
build()
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
null
|
|
|
|
}
|
|
|
|
return currentAudioFocusRequest
|
|
|
|
}
|
2021-07-19 13:19:47 +00:00
|
|
|
}
|