feat(ui): ability to play any local video from files using file chooser (#1158)

This commit is contained in:
IndusAryan 2024-07-02 03:04:36 +05:30 committed by GitHub
parent 1a05651510
commit a5582a7a67
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 135 additions and 59 deletions

View file

@ -1,8 +1,11 @@
package com.lagradost.cloudstream3.ui.download package com.lagradost.cloudstream3.ui.download
import android.app.Activity
import android.app.Dialog import android.app.Dialog
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.format.Formatter.formatShortFileSize import android.text.format.Formatter.formatShortFileSize
@ -12,6 +15,7 @@ import android.view.ViewGroup
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isGone import androidx.core.view.isGone
@ -30,6 +34,7 @@ import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownload
import com.lagradost.cloudstream3.ui.player.BasicLink import com.lagradost.cloudstream3.ui.player.BasicLink
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
import com.lagradost.cloudstream3.ui.player.LinkGenerator import com.lagradost.cloudstream3.ui.player.LinkGenerator
import com.lagradost.cloudstream3.ui.player.OfflinePlaybackHelper.playUri
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
import com.lagradost.cloudstream3.ui.result.setLinearListLayout import com.lagradost.cloudstream3.ui.result.setLinearListLayout
import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.TV
@ -137,9 +142,15 @@ class DownloadFragment : Fragment() {
) )
} }
binding?.downloadStreamButton?.apply { binding?.apply {
isGone = isLayout(TV) openLocalVideoButton.apply {
setOnClickListener { showStreamInputDialog(it.context) } isGone = isLayout(TV)
setOnClickListener { openLocalVideo() }
}
downloadStreamButton.apply {
isGone = isLayout(TV)
setOnClickListener { showStreamInputDialog(it.context) }
}
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@ -189,6 +200,22 @@ class DownloadFragment : Fragment() {
view?.setLayoutWidth(bytes) view?.setLayoutWidth(bytes)
} }
private fun openLocalVideo() {
val intent = Intent()
.setAction(Intent.ACTION_GET_CONTENT)
.setType("video/*")
.addCategory(Intent.CATEGORY_OPENABLE)
.addFlags(FLAG_GRANT_READ_URI_PERMISSION) // Request temporary access
normalSafeApiCall {
videoResultLauncher.launch(
Intent.createChooser(
intent,
getString(R.string.open_local_video)
)
)
}
}
private fun showStreamInputDialog(context: Context) { private fun showStreamInputDialog(context: Context) {
val dialog = Dialog(context, R.style.AlertDialogCustom) val dialog = Dialog(context, R.style.AlertDialogCustom)
val binding = StreamInputBinding.inflate(dialog.layoutInflater) val binding = StreamInputBinding.inflate(dialog.layoutInflater)
@ -247,4 +274,13 @@ class DownloadFragment : Fragment() {
binding?.downloadStreamButton?.extend() binding?.downloadStreamButton?.extend()
} }
} }
}
// Open local video from files using content provider x safeFile
private val videoResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
val selectedVideoUri = result?.data?.data ?: return@registerForActivityResult
playUri(activity ?: return@registerForActivityResult, selectedVideoUri)
}
}

View file

@ -1,8 +1,6 @@
package com.lagradost.cloudstream3.ui.player package com.lagradost.cloudstream3.ui.player
import android.content.ContentUris
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.KeyEvent import android.view.KeyEvent
@ -10,13 +8,13 @@ import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.CommonActivity
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.utils.ExtractorUri import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.ui.player.OfflinePlaybackHelper.playLink
import com.lagradost.safefile.SafeFile import com.lagradost.cloudstream3.ui.player.OfflinePlaybackHelper.playUri
const val DTAG = "PlayerActivity"
class DownloadedPlayerActivity : AppCompatActivity() { class DownloadedPlayerActivity : AppCompatActivity() {
private val dTAG = "DownloadedPlayerAct"
override fun dispatchKeyEvent(event: KeyEvent): Boolean { override fun dispatchKeyEvent(event: KeyEvent): Boolean {
CommonActivity.dispatchKeyEvent(this, event)?.let { CommonActivity.dispatchKeyEvent(this, event)?.let {
return it return it
@ -35,53 +33,18 @@ class DownloadedPlayerActivity : AppCompatActivity() {
CommonActivity.onUserLeaveHint(this) CommonActivity.onUserLeaveHint(this)
} }
private fun playLink(url: String) {
this.navigate(
R.id.global_to_navigation_player, GeneratorPlayer.newInstance(
LinkGenerator(
listOf(
BasicLink(url)
)
)
)
)
}
private fun playUri(uri: Uri) {
val name = SafeFile.fromUri(this, uri)?.name()
this.navigate(
R.id.global_to_navigation_player, GeneratorPlayer.newInstance(
DownloadFileGenerator(
listOf(
ExtractorUri(
uri = uri,
name = name ?: getString(R.string.downloaded_file),
// well not the same as a normal id, but we take it as users may want to
// play downloaded files and save the location
id = kotlin.runCatching { ContentUris.parseId(uri) }.getOrNull()?.hashCode()
)
)
)
)
)
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
Log.i(DTAG, "onCreate")
CommonActivity.loadThemes(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
CommonActivity.loadThemes(this)
CommonActivity.init(this) CommonActivity.init(this)
setContentView(R.layout.empty_layout) setContentView(R.layout.empty_layout)
Log.i(dTAG, "onCreate")
val data = intent.data val data = intent.data
if (intent?.action == Intent.ACTION_SEND) { if (intent?.action == Intent.ACTION_SEND) {
val extraText = try { // I dont trust android val extraText = normalSafeApiCall { // I dont trust android
intent.getStringExtra(Intent.EXTRA_TEXT) intent.getStringExtra(Intent.EXTRA_TEXT)
} catch (e: Exception) {
null
} }
val cd = intent.clipData val cd = intent.clipData
val item = if (cd != null && cd.itemCount > 0) cd.getItemAt(0) else null val item = if (cd != null && cd.itemCount > 0) cd.getItemAt(0) else null
@ -89,19 +52,19 @@ class DownloadedPlayerActivity : AppCompatActivity() {
// idk what I am doing, just hope any of these work // idk what I am doing, just hope any of these work
if (item?.uri != null) if (item?.uri != null)
playUri(item.uri) playUri(this, item.uri)
else if (url != null) else if (url != null)
playLink(url) playLink(this, url)
else if (data != null) else if (data != null)
playUri(data) playUri(this, data)
else if (extraText != null) else if (extraText != null)
playLink(extraText) playLink(this, extraText)
else { else {
finish() finish()
return return
} }
} else if (data?.scheme == "content") { } else if (data?.scheme == "content") {
playUri(data) playUri(this, data)
} else { } else {
finish() finish()
return return

View file

@ -1,9 +1,12 @@
package com.lagradost.cloudstream3.ui.player package com.lagradost.cloudstream3.ui.player
import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.amap
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.ExtractorUri
import java.net.URI import com.lagradost.cloudstream3.utils.INFER_TYPE
import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.loadExtractor
import com.lagradost.cloudstream3.utils.unshortenLinkSafe
/** /**
* Used to open the player more easily with the LinkGenerator * Used to open the player more easily with the LinkGenerator

View file

@ -0,0 +1,43 @@
package com.lagradost.cloudstream3.ui.player
import android.app.Activity
import android.content.ContentUris
import android.net.Uri
import androidx.core.content.ContextCompat.getString
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.utils.ExtractorUri
import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.safefile.SafeFile
object OfflinePlaybackHelper {
fun playLink(activity: Activity, url: String) {
activity.navigate(
R.id.global_to_navigation_player, GeneratorPlayer.newInstance(
LinkGenerator(
listOf(
BasicLink(url)
)
)
)
)
}
fun playUri(activity: Activity, uri: Uri) {
val name = SafeFile.fromUri(activity, uri)?.name()
activity.navigate(
R.id.global_to_navigation_player, GeneratorPlayer.newInstance(
DownloadFileGenerator(
listOf(
ExtractorUri(
uri = uri,
name = name ?: getString(activity, R.string.downloaded_file),
// well not the same as a normal id, but we take it as users may want to
// play downloaded files and save the location
id = kotlin.runCatching { ContentUris.parseId(uri) }.getOrNull()?.hashCode()
)
)
)
)
)
}
}

View file

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/white"
android:pathData="M3.9,12c0,-1.71 1.39,-3.1 3.1,-3.1h4L11,7L7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-1.9L7,15.1c-1.71,0 -3.1,-1.39 -3.1,-3.1zM8,13h8v-2L8,11v2zM17,7h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1s-1.39,3.1 -3.1,3.1h-4L13,17h4c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5z" />
</vector>

View file

@ -198,11 +198,30 @@
</LinearLayout> </LinearLayout>
</com.facebook.shimmer.ShimmerFrameLayout> </com.facebook.shimmer.ShimmerFrameLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="bottom|end">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/open_local_video_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?attr/floatingActionButtonSmallStyle"
android:backgroundTint="?attr/primaryGrayBackground"
android:src="@drawable/netflix_play"
android:layout_marginEnd="16dp"
android:tooltipText="@string/open_local_video"
android:layout_gravity="bottom|end"
android:contentDescription="@string/open_local_video" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/download_stream_button" android:id="@+id/download_stream_button"
style="@style/ExtendedFloatingActionButton" style="@style/ExtendedFloatingActionButton"
android:text="@string/stream" android:text="@string/stream"
android:textColor="?attr/textColor" android:textColor="?attr/textColor"
app:icon="@drawable/netflix_play" app:icon="@drawable/ic_network_stream"
tools:ignore="ContentDescription" /> android:contentDescription="@string/stream" />
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -151,6 +151,7 @@
<string name="download_format" translatable="false">%s - %s</string> <string name="download_format" translatable="false">%s - %s</string>
<string name="update_started">Update Started</string> <string name="update_started">Update Started</string>
<string name="stream">Network stream</string> <string name="stream">Network stream</string>
<string name="open_local_video">Open local video</string>
<string name="error_loading_links_toast">Error Loading Links</string> <string name="error_loading_links_toast">Error Loading Links</string>
<string name="links_reloaded_toast">Links Reloaded</string> <string name="links_reloaded_toast">Links Reloaded</string>
<string name="download_storage_text">Internal Storage</string> <string name="download_storage_text">Internal Storage</string>