mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
android tv recc not working :(
This commit is contained in:
parent
c5d53d7621
commit
1bbbbce326
7 changed files with 197 additions and 34 deletions
|
@ -88,6 +88,7 @@ repositories {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation 'com.google.android.mediahome:video:1.0.0'
|
||||||
testImplementation 'org.json:json:20180813'
|
testImplementation 'org.json:json:20180813'
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
|
@ -95,10 +96,10 @@ dependencies {
|
||||||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||||
implementation 'com.google.android.material:material:1.5.0'
|
implementation 'com.google.android.material:material:1.5.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
||||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.0-alpha01'
|
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.0-alpha02'
|
||||||
implementation 'androidx.navigation:navigation-ui-ktx:2.5.0-alpha01'
|
implementation 'androidx.navigation:navigation-ui-ktx:2.5.0-alpha02'
|
||||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0'
|
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||||
|
@ -167,4 +168,6 @@ dependencies {
|
||||||
|
|
||||||
// for shimmer when loading
|
// for shimmer when loading
|
||||||
implementation 'com.facebook.shimmer:shimmer:0.5.0'
|
implementation 'com.facebook.shimmer:shimmer:0.5.0'
|
||||||
|
|
||||||
|
implementation "androidx.tvprovider:tvprovider:1.0.0"
|
||||||
}
|
}
|
|
@ -9,6 +9,7 @@
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
|
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
||||||
|
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
|
||||||
|
|
||||||
<uses-feature android:name="android.hardware.touchscreen"
|
<uses-feature android:name="android.hardware.touchscreen"
|
||||||
android:required="false"/>
|
android:required="false"/>
|
||||||
|
@ -137,6 +138,7 @@
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.provider"
|
android:authorities="${applicationId}.provider"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
android:enabled="true"
|
||||||
android:grantUriPermissions="true">
|
android:grantUriPermissions="true">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
|
|
@ -3,14 +3,16 @@ package com.lagradost.cloudstream3.ui.home
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.SearchResponse
|
import com.lagradost.cloudstream3.SearchResponse
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
||||||
|
import com.lagradost.cloudstream3.ui.search.SearchResponseDiffCallback
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
||||||
|
|
||||||
class HomeChildItemAdapter(
|
class HomeChildItemAdapter(
|
||||||
var cardList: List<SearchResponse>,
|
val cardList: MutableList<SearchResponse>,
|
||||||
val layout: Int = R.layout.home_result_grid,
|
val layout: Int = R.layout.home_result_grid,
|
||||||
private val nextFocusUp: Int? = null,
|
private val nextFocusUp: Int? = null,
|
||||||
private val nextFocusDown: Int? = null,
|
private val nextFocusDown: Int? = null,
|
||||||
|
@ -44,6 +46,17 @@ class HomeChildItemAdapter(
|
||||||
return (cardList[position].id ?: position).toLong()
|
return (cardList[position].id ?: position).toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateList(newList: List<SearchResponse>) {
|
||||||
|
val diffResult = DiffUtil.calculateDiff(
|
||||||
|
SearchResponseDiffCallback(this.cardList, newList)
|
||||||
|
)
|
||||||
|
|
||||||
|
cardList.clear()
|
||||||
|
cardList.addAll(newList)
|
||||||
|
|
||||||
|
diffResult.dispatchUpdatesTo(this)
|
||||||
|
}
|
||||||
|
|
||||||
class CardViewHolder
|
class CardViewHolder
|
||||||
constructor(
|
constructor(
|
||||||
itemView: View, private val clickCallback: (SearchClickCallback) -> Unit, private val itemCount: Int,
|
itemView: View, private val clickCallback: (SearchClickCallback) -> Unit, private val itemCount: Int,
|
||||||
|
|
|
@ -124,7 +124,7 @@ class HomeFragment : Fragment() {
|
||||||
|
|
||||||
val spanListener = { span: Int ->
|
val spanListener = { span: Int ->
|
||||||
recycle.spanCount = span
|
recycle.spanCount = span
|
||||||
(recycle.adapter as SearchAdapter).notifyDataSetChanged()
|
//(recycle.adapter as SearchAdapter).notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
configEvent += spanListener
|
configEvent += spanListener
|
||||||
|
@ -133,7 +133,7 @@ class HomeFragment : Fragment() {
|
||||||
configEvent -= spanListener
|
configEvent -= spanListener
|
||||||
}
|
}
|
||||||
|
|
||||||
(recycle.adapter as SearchAdapter).notifyDataSetChanged()
|
//(recycle.adapter as SearchAdapter).notifyDataSetChanged()
|
||||||
|
|
||||||
bottomSheetDialogBuilder.show()
|
bottomSheetDialogBuilder.show()
|
||||||
}
|
}
|
||||||
|
@ -399,7 +399,7 @@ class HomeFragment : Fragment() {
|
||||||
val randomSize = items.size
|
val randomSize = items.size
|
||||||
home_main_poster_recyclerview?.adapter =
|
home_main_poster_recyclerview?.adapter =
|
||||||
HomeChildItemAdapter(
|
HomeChildItemAdapter(
|
||||||
items,
|
items.toMutableList(),
|
||||||
R.layout.home_result_big_grid,
|
R.layout.home_result_big_grid,
|
||||||
nextFocusUp = home_main_poster_recyclerview.nextFocusUpId,
|
nextFocusUp = home_main_poster_recyclerview.nextFocusUpId,
|
||||||
nextFocusDown = home_main_poster_recyclerview.nextFocusDownId
|
nextFocusDown = home_main_poster_recyclerview.nextFocusDownId
|
||||||
|
@ -438,7 +438,7 @@ class HomeFragment : Fragment() {
|
||||||
val d = data.value
|
val d = data.value
|
||||||
|
|
||||||
currentHomePage = d
|
currentHomePage = d
|
||||||
(home_master_recycler?.adapter as ParentItemAdapter?)?.updateList(
|
(home_master_recycler?.adapter as? ParentItemAdapter?)?.updateList(
|
||||||
d?.items?.mapNotNull {
|
d?.items?.mapNotNull {
|
||||||
try {
|
try {
|
||||||
HomePageList(it.name, it.list.filterSearchResponse())
|
HomePageList(it.name, it.list.filterSearchResponse())
|
||||||
|
@ -483,6 +483,7 @@ class HomeFragment : Fragment() {
|
||||||
home_loaded?.isVisible = false
|
home_loaded?.isVisible = false
|
||||||
}
|
}
|
||||||
is Resource.Loading -> {
|
is Resource.Loading -> {
|
||||||
|
(home_master_recycler?.adapter as? ParentItemAdapter?)?.updateList(listOf())
|
||||||
home_loading_shimmer?.startShimmer()
|
home_loading_shimmer?.startShimmer()
|
||||||
home_loading?.isVisible = true
|
home_loading?.isVisible = true
|
||||||
home_loading_error?.isVisible = false
|
home_loading_error?.isVisible = false
|
||||||
|
@ -556,13 +557,12 @@ class HomeFragment : Fragment() {
|
||||||
home_bookmarked_parent_item_title?.text = getString(availableWatchStatusTypes.first.stringRes)*/
|
home_bookmarked_parent_item_title?.text = getString(availableWatchStatusTypes.first.stringRes)*/
|
||||||
}
|
}
|
||||||
|
|
||||||
observe(homeViewModel.bookmarks) { pair ->
|
observe(homeViewModel.bookmarks) { (isVis, bookmarks) ->
|
||||||
home_bookmarked_holder.isVisible = pair.first
|
home_bookmarked_holder.isVisible = isVis
|
||||||
|
|
||||||
val bookmarks = pair.second
|
(home_bookmarked_child_recyclerview?.adapter as? HomeChildItemAdapter?)?.updateList(
|
||||||
(home_bookmarked_child_recyclerview?.adapter as HomeChildItemAdapter?)?.cardList =
|
|
||||||
bookmarks
|
bookmarks
|
||||||
home_bookmarked_child_recyclerview?.adapter?.notifyDataSetChanged()
|
)
|
||||||
|
|
||||||
home_bookmarked_child_more_info?.setOnClickListener {
|
home_bookmarked_child_more_info?.setOnClickListener {
|
||||||
activity?.loadHomepageList(
|
activity?.loadHomepageList(
|
||||||
|
@ -576,9 +576,15 @@ class HomeFragment : Fragment() {
|
||||||
|
|
||||||
observe(homeViewModel.resumeWatching) { resumeWatching ->
|
observe(homeViewModel.resumeWatching) { resumeWatching ->
|
||||||
home_watch_holder?.isVisible = resumeWatching.isNotEmpty()
|
home_watch_holder?.isVisible = resumeWatching.isNotEmpty()
|
||||||
(home_watch_child_recyclerview?.adapter as HomeChildItemAdapter?)?.cardList =
|
(home_watch_child_recyclerview?.adapter as? HomeChildItemAdapter?)?.updateList(
|
||||||
resumeWatching
|
resumeWatching
|
||||||
home_watch_child_recyclerview?.adapter?.notifyDataSetChanged()
|
)
|
||||||
|
|
||||||
|
//if (context?.isTvSettings() == true) {
|
||||||
|
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
// context?.addProgramsToContinueWatching(resumeWatching.mapNotNull { it as? DataStoreHelper.ResumeWatchingResult })
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
home_watch_child_more_info?.setOnClickListener {
|
home_watch_child_more_info?.setOnClickListener {
|
||||||
activity?.loadHomepageList(
|
activity?.loadHomepageList(
|
||||||
|
|
|
@ -65,12 +65,12 @@ class ParentItemAdapter(
|
||||||
fun bind(info: HomePageList) {
|
fun bind(info: HomePageList) {
|
||||||
title.text = info.name
|
title.text = info.name
|
||||||
recyclerView.adapter = HomeChildItemAdapter(
|
recyclerView.adapter = HomeChildItemAdapter(
|
||||||
info.list,
|
info.list.toMutableList(),
|
||||||
clickCallback = clickCallback,
|
clickCallback = clickCallback,
|
||||||
nextFocusUp = recyclerView.nextFocusUpId,
|
nextFocusUp = recyclerView.nextFocusUpId,
|
||||||
nextFocusDown = recyclerView.nextFocusDownId
|
nextFocusDown = recyclerView.nextFocusDownId
|
||||||
)
|
)
|
||||||
(recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged()
|
//(recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged()
|
||||||
|
|
||||||
moreInfo.setOnClickListener {
|
moreInfo.setOnClickListener {
|
||||||
moreInfoClickCallback.invoke(info)
|
moreInfoClickCallback.invoke(info)
|
||||||
|
|
|
@ -1183,9 +1183,9 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
|
||||||
result_episode_loading?.isVisible = false
|
result_episode_loading?.isVisible = false
|
||||||
if (result_episodes == null || result_episodes.adapter == null) return@observe
|
if (result_episodes == null || result_episodes.adapter == null) return@observe
|
||||||
currentEpisodes = episodes.value
|
currentEpisodes = episodes.value
|
||||||
(result_episodes?.adapter as EpisodeAdapter?)?.cardList = episodes.value
|
(result_episodes?.adapter as? EpisodeAdapter?)?.cardList = episodes.value
|
||||||
(result_episodes?.adapter as EpisodeAdapter?)?.updateLayout()
|
(result_episodes?.adapter as? EpisodeAdapter?)?.updateLayout()
|
||||||
(result_episodes?.adapter as EpisodeAdapter?)?.notifyDataSetChanged()
|
(result_episodes?.adapter as? EpisodeAdapter?)?.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.lagradost.cloudstream3.utils
|
package com.lagradost.cloudstream3.utils
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
|
@ -10,6 +11,7 @@ import android.database.Cursor
|
||||||
import android.media.AudioAttributes
|
import android.media.AudioAttributes
|
||||||
import android.media.AudioFocusRequest
|
import android.media.AudioFocusRequest
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
|
import android.media.tv.TvContract.Channels.COLUMN_INTERNAL_PROVIDER_ID
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.net.NetworkCapabilities
|
import android.net.NetworkCapabilities
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
@ -18,20 +20,23 @@ import android.os.Environment
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
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
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
import com.google.android.gms.cast.framework.CastContext
|
import com.google.android.gms.cast.framework.CastContext
|
||||||
import com.google.android.gms.cast.framework.CastState
|
import com.google.android.gms.cast.framework.CastState
|
||||||
import com.google.android.gms.common.ConnectionResult
|
import com.google.android.gms.common.ConnectionResult
|
||||||
import com.google.android.gms.common.GoogleApiAvailability
|
import com.google.android.gms.common.GoogleApiAvailability
|
||||||
import com.google.android.gms.common.wrappers.Wrappers
|
import com.google.android.gms.common.wrappers.Wrappers
|
||||||
import com.lagradost.cloudstream3.MainActivity
|
import com.lagradost.cloudstream3.*
|
||||||
import com.lagradost.cloudstream3.R
|
|
||||||
import com.lagradost.cloudstream3.SearchResponse
|
|
||||||
import com.lagradost.cloudstream3.mapper
|
|
||||||
import com.lagradost.cloudstream3.movieproviders.MeloMovieProvider
|
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.ui.result.ResultFragment
|
import com.lagradost.cloudstream3.ui.result.ResultFragment
|
||||||
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
import com.lagradost.cloudstream3.utils.FillerEpisodeCheck.toClassDir
|
import com.lagradost.cloudstream3.utils.FillerEpisodeCheck.toClassDir
|
||||||
import com.lagradost.cloudstream3.utils.JsUnpacker.Companion.load
|
import com.lagradost.cloudstream3.utils.JsUnpacker.Companion.load
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||||
|
@ -41,6 +46,136 @@ import java.net.URL
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
|
|
||||||
object AppUtils {
|
object AppUtils {
|
||||||
|
//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
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
|
private fun buildWatchNextProgramUri(
|
||||||
|
context: Context,
|
||||||
|
card: DataStoreHelper.ResumeWatchingResult
|
||||||
|
): WatchNextProgram {
|
||||||
|
val isSeries = !card.type.isMovieType()
|
||||||
|
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
|
||||||
|
|
||||||
|
ioSafe {
|
||||||
|
data.forEach { episodeInfo ->
|
||||||
|
try {
|
||||||
|
val (program, id) = getWatchNextProgramByVideoId(episodeInfo.url, this)
|
||||||
|
val nextProgram = buildWatchNextProgramUri(this, episodeInfo)
|
||||||
|
|
||||||
|
// If the program is already in the Watch Next row, update it
|
||||||
|
if (program != null && id != null) {
|
||||||
|
PreviewChannelHelper(this).updateWatchNextProgram(
|
||||||
|
nextProgram,
|
||||||
|
id,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
PreviewChannelHelper(this)
|
||||||
|
.publishWatchNextProgram(nextProgram)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("Range")
|
||||||
fun getVideoContentUri(context: Context, videoFilePath: String): Uri? {
|
fun getVideoContentUri(context: Context, videoFilePath: String): Uri? {
|
||||||
val cursor = context.contentResolver.query(
|
val cursor = context.contentResolver.query(
|
||||||
MediaStore.Video.Media.EXTERNAL_CONTENT_URI, arrayOf(MediaStore.Video.Media._ID),
|
MediaStore.Video.Media.EXTERNAL_CONTENT_URI, arrayOf(MediaStore.Video.Media._ID),
|
||||||
|
@ -94,14 +229,14 @@ object AppUtils {
|
||||||
return mapper.writeValueAsString(this)
|
return mapper.writeValueAsString(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T> parseJson(value : String): T {
|
inline fun <reified T> parseJson(value: String): T {
|
||||||
return mapper.readValue(value)
|
return mapper.readValue(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T> tryParseJson(value : String): T? {
|
inline fun <reified T> tryParseJson(value: String): T? {
|
||||||
return try {
|
return try {
|
||||||
parseJson(value)
|
parseJson(value)
|
||||||
} catch (_ : Exception) {
|
} catch (_: Exception) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,7 +303,7 @@ object AppUtils {
|
||||||
startAction: Int = 0,
|
startAction: Int = 0,
|
||||||
startValue: Int = 0
|
startValue: Int = 0
|
||||||
) {
|
) {
|
||||||
(this as AppCompatActivity?)?.loadResult(card.url, card.apiName, startAction, startValue)
|
(this as? AppCompatActivity?)?.loadResult(card.url, card.apiName, startAction, startValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Activity.requestLocalAudioFocus(focusRequest: AudioFocusRequest?) {
|
fun Activity.requestLocalAudioFocus(focusRequest: AudioFocusRequest?) {
|
||||||
|
@ -230,6 +365,7 @@ object AppUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copied from https://github.com/videolan/vlc-android/blob/master/application/vlc-android/src/org/videolan/vlc/util/FileUtils.kt
|
// Copied from https://github.com/videolan/vlc-android/blob/master/application/vlc-android/src/org/videolan/vlc/util/FileUtils.kt
|
||||||
|
@SuppressLint("Range")
|
||||||
fun Context.getUri(data: Uri?): Uri? {
|
fun Context.getUri(data: Uri?): Uri? {
|
||||||
var uri = data
|
var uri = data
|
||||||
val ctx = this
|
val ctx = this
|
||||||
|
@ -245,11 +381,13 @@ object AppUtils {
|
||||||
arrayOf(MediaStore.MediaColumns.DISPLAY_NAME), null, null, null
|
arrayOf(MediaStore.MediaColumns.DISPLAY_NAME), null, null, null
|
||||||
)
|
)
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
val filename = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME))
|
val filename =
|
||||||
.replace("/", "")
|
cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME))
|
||||||
|
.replace("/", "")
|
||||||
inputStream = ctx.contentResolver.openInputStream(data)
|
inputStream = ctx.contentResolver.openInputStream(data)
|
||||||
if (inputStream == null) return data
|
if (inputStream == null) return data
|
||||||
os = FileOutputStream(Environment.getExternalStorageDirectory().path + "/Download/" + filename)
|
os =
|
||||||
|
FileOutputStream(Environment.getExternalStorageDirectory().path + "/Download/" + filename)
|
||||||
val buffer = ByteArray(1024)
|
val buffer = ByteArray(1024)
|
||||||
var bytesRead = inputStream.read(buffer)
|
var bytesRead = inputStream.read(buffer)
|
||||||
while (bytesRead >= 0) {
|
while (bytesRead >= 0) {
|
||||||
|
@ -272,7 +410,8 @@ object AppUtils {
|
||||||
arrayOf(MediaStore.Video.Media.DATA), null, null, null
|
arrayOf(MediaStore.Video.Media.DATA), null, null, null
|
||||||
)?.use {
|
)?.use {
|
||||||
val columnIndex = it.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)
|
val columnIndex = it.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)
|
||||||
if (it.moveToFirst()) Uri.fromFile(File(it.getString(columnIndex))) ?: data else data
|
if (it.moveToFirst()) Uri.fromFile(File(it.getString(columnIndex)))
|
||||||
|
?: data else data
|
||||||
}
|
}
|
||||||
//uri = MediaUtils.getContentMediaUri(data)
|
//uri = MediaUtils.getContentMediaUri(data)
|
||||||
/*} else if (data.authority == ctx.getString(R.string.tv_provider_authority)) {
|
/*} else if (data.authority == ctx.getString(R.string.tv_provider_authority)) {
|
||||||
|
|
Loading…
Reference in a new issue