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

feat: Transcoding stream in player selection + Transcoded downloads #791

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
22 changes: 22 additions & 0 deletions app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import androidx.media3.ui.DefaultTimeBar
import androidx.media3.ui.PlayerControlView
import androidx.media3.ui.PlayerView
import androidx.navigation.navArgs
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.databinding.ActivityPlayerBinding
import dev.jdtech.jellyfin.dialogs.SpeedSelectionDialogFragment
Expand Down Expand Up @@ -82,6 +83,10 @@ class PlayerActivity : BasePlayerActivity() {

binding = ActivityPlayerBinding.inflate(layoutInflater)
setContentView(binding.root)
val changeQualityButton: ImageButton = findViewById(R.id.btnChangeQuality)
changeQualityButton.setOnClickListener {
showQualitySelectionDialog()
}
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)

binding.playerView.player = viewModel.player
Expand Down Expand Up @@ -342,6 +347,23 @@ class PlayerActivity : BasePlayerActivity() {
} catch (_: IllegalArgumentException) { }
}

private fun showQualitySelectionDialog() {
val height = viewModel.getOriginalHeight() // TODO: rewrite getting height stuff I don't like that its only update after changing quality
val qualities = when (height) {
0 -> arrayOf("Auto", "Original - Max", "720p - 2Mbps", "480p - 1Mbps", "360p - 800kbps")
nomadics9 marked this conversation as resolved.
Show resolved Hide resolved
in 1001..1999 -> arrayOf("Auto", "Original (1080p) - Max", "720p - 2Mbps", "480p - 1Mbps", "360p - 800kbps")
in 2000..3000 -> arrayOf("Auto", "Original (4K) - Max", "720p - 2Mbps", "480p - 1Mbps", "360p - 800kbps")
else -> arrayOf("Auto", "Original - Max", "720p - 2Mbps", "480p - 1Mbps", "360p - 800kbps")
}
MaterialAlertDialogBuilder(this)
.setTitle("Select Video Quality")
.setItems(qualities) { _, which ->
val selectedQuality = qualities[which]
viewModel.changeVideoQuality(selectedQuality)
}
.show()
}

override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean,
newConfig: Configuration,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,70 +157,80 @@ class EpisodeBottomSheetFragment : BottomSheetDialogFragment() {
}

binding.itemActions.downloadButton.setOnClickListener {
if (viewModel.item.isDownloaded()) {
viewModel.deleteEpisode()
binding.itemActions.downloadButton.setIconResource(CoreR.drawable.ic_download)
} else if (viewModel.item.isDownloading()) {
createCancelDialog()
} else {
binding.itemActions.downloadButton.setIconResource(AndroidR.color.transparent)
binding.itemActions.progressDownload.isIndeterminate = true
binding.itemActions.progressDownload.isVisible = true
if (requireContext().getExternalFilesDirs(null).filterNotNull().size > 1) {
val storageDialog = getStorageSelectionDialog(
requireContext(),
onItemSelected = { storageIndex ->
if (viewModel.item.sources.size > 1) {
val dialog = getVideoVersionDialog(
requireContext(),
viewModel.item,
onItemSelected = { sourceIndex ->
createDownloadPreparingDialog()
viewModel.download(sourceIndex, storageIndex)
},
onCancel = {
binding.itemActions.progressDownload.isVisible = false
binding.itemActions.downloadButton.setIconResource(CoreR.drawable.ic_download)
},
)
dialog.show()
return@getStorageSelectionDialog
}
createDownloadPreparingDialog()
viewModel.download(storageIndex = storageIndex)
},
onCancel = {
binding.itemActions.progressDownload.isVisible = false
binding.itemActions.downloadButton.setIconResource(CoreR.drawable.ic_download)
},
)
storageDialog.show()
return@setOnClickListener
}
if (viewModel.item.sources.size > 1) {
val dialog = getVideoVersionDialog(
requireContext(),
viewModel.item,
onItemSelected = { sourceIndex ->
createDownloadPreparingDialog()
viewModel.download(sourceIndex)
},
onCancel = {
binding.itemActions.progressDownload.isVisible = false
binding.itemActions.downloadButton.setIconResource(CoreR.drawable.ic_download)
},
)
dialog.show()
return@setOnClickListener
}
createDownloadPreparingDialog()
viewModel.download()
}
handleDownload()
}

return binding.root
}

private fun handleDownload() {
if (viewModel.item.isDownloaded()) {
viewModel.deleteEpisode()
binding.itemActions.downloadButton.setIconResource(CoreR.drawable.ic_download)
} else if (viewModel.item.isDownloading()) {
createCancelDialog()
}else if (!appPreferences.downloadQualityDefault) {
createPickQualityDialog()
} else {
download()
}
}

private fun download(){
binding.itemActions.downloadButton.setIconResource(AndroidR.color.transparent)
binding.itemActions.progressDownload.isIndeterminate = true
binding.itemActions.progressDownload.isVisible = true
if (requireContext().getExternalFilesDirs(null).filterNotNull().size > 1) {
val storageDialog = getStorageSelectionDialog(
requireContext(),
onItemSelected = { storageIndex ->
if (viewModel.item.sources.size > 1) {
val dialog = getVideoVersionDialog(
requireContext(),
viewModel.item,
onItemSelected = { sourceIndex ->
createDownloadPreparingDialog()
viewModel.download(sourceIndex, storageIndex)
},
onCancel = {
binding.itemActions.progressDownload.isVisible = false
binding.itemActions.downloadButton.setIconResource(CoreR.drawable.ic_download)
},
)
dialog.show()
return@getStorageSelectionDialog
}
createDownloadPreparingDialog()
viewModel.download(storageIndex = storageIndex)
},
onCancel = {
binding.itemActions.progressDownload.isVisible = false
binding.itemActions.downloadButton.setIconResource(CoreR.drawable.ic_download)
},
)
storageDialog.show()
return
}
if (viewModel.item.sources.size > 1) {
val dialog = getVideoVersionDialog(
requireContext(),
viewModel.item,
onItemSelected = { sourceIndex ->
createDownloadPreparingDialog()
viewModel.download(sourceIndex)
},
onCancel = {
binding.itemActions.progressDownload.isVisible = false
binding.itemActions.downloadButton.setIconResource(CoreR.drawable.ic_download)
},
)
dialog.show()
return
}
createDownloadPreparingDialog()
viewModel.download()
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
dialog?.let {
val sheet = it as BottomSheetDialog
Expand Down Expand Up @@ -402,6 +412,31 @@ class EpisodeBottomSheetFragment : BottomSheetDialogFragment() {
dialog.show()
}

private fun createPickQualityDialog() {
val qualityEntries = resources.getStringArray(CoreR.array.quality_entries)
val qualityValues = resources.getStringArray(CoreR.array.quality_values)
val quality = appPreferences.downloadQuality
val currentQualityIndex = qualityValues.indexOf(quality)
var selectedQuality = quality


val builder = MaterialAlertDialogBuilder(requireContext())
builder.setTitle("Download Quality")
builder.setSingleChoiceItems(qualityEntries, currentQualityIndex) { _, which ->
selectedQuality = qualityValues[which]
}
builder.setPositiveButton("Download") { dialog, _ ->
appPreferences.downloadQuality = selectedQuality
dialog.dismiss()
download()
}
builder.setNegativeButton("Cancel") { dialog, _ ->
dialog.dismiss()
}
val dialog = builder.create()
dialog.show()
}

private fun navigateToPlayerActivity(
playerItems: Array<PlayerItem>,
) {
Expand Down
153 changes: 94 additions & 59 deletions app/phone/src/main/java/dev/jdtech/jellyfin/fragments/MovieFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -192,72 +192,82 @@ class MovieFragment : Fragment() {
}

binding.itemActions.downloadButton.setOnClickListener {
if (viewModel.item.isDownloaded()) {
viewModel.deleteItem()
binding.itemActions.downloadButton.setIconResource(CoreR.drawable.ic_download)
} else if (viewModel.item.isDownloading()) {
createCancelDialog()
} else {
binding.itemActions.downloadButton.setIconResource(android.R.color.transparent)
binding.itemActions.progressDownload.isIndeterminate = true
binding.itemActions.progressDownload.isVisible = true
if (requireContext().getExternalFilesDirs(null).filterNotNull().size > 1) {
val storageDialog = getStorageSelectionDialog(
requireContext(),
onItemSelected = { storageIndex ->
if (viewModel.item.sources.size > 1) {
val dialog = getVideoVersionDialog(
requireContext(),
viewModel.item,
onItemSelected = { sourceIndex ->
createDownloadPreparingDialog()
viewModel.download(sourceIndex, storageIndex)
},
onCancel = {
binding.itemActions.progressDownload.isVisible = false
binding.itemActions.downloadButton.setIconResource(CoreR.drawable.ic_download)
},
)
dialog.show()
return@getStorageSelectionDialog
}
createDownloadPreparingDialog()
viewModel.download(storageIndex = storageIndex)
},
onCancel = {
binding.itemActions.progressDownload.isVisible = false
binding.itemActions.downloadButton.setIconResource(CoreR.drawable.ic_download)
},
)
storageDialog.show()
return@setOnClickListener
}
if (viewModel.item.sources.size > 1) {
val dialog = getVideoVersionDialog(
requireContext(),
viewModel.item,
onItemSelected = { sourceIndex ->
createDownloadPreparingDialog()
viewModel.download(sourceIndex)
},
onCancel = {
binding.itemActions.progressDownload.isVisible = false
binding.itemActions.downloadButton.setIconResource(CoreR.drawable.ic_download)
},
)
dialog.show()
return@setOnClickListener
}
createDownloadPreparingDialog()
viewModel.download()
}
handleDownload()
}

binding.peopleRecyclerView.adapter = PersonListAdapter { person ->
navigateToPersonDetail(person.id)
}
}

private fun handleDownload() {
nomadics9 marked this conversation as resolved.
Show resolved Hide resolved
if (viewModel.item.isDownloaded()) {
viewModel.deleteItem()
binding.itemActions.downloadButton.setIconResource(CoreR.drawable.ic_download)
} else if (viewModel.item.isDownloading()) {
createCancelDialog()
} else if (!appPreferences.downloadQualityDefault) {
createPickQualityDialog()
} else {
download()
}
}

private fun download() {
binding.itemActions.downloadButton.setIconResource(android.R.color.transparent)
binding.itemActions.progressDownload.isIndeterminate = true
binding.itemActions.progressDownload.isVisible = true
if (requireContext().getExternalFilesDirs(null).filterNotNull().size > 1) {
val storageDialog = getStorageSelectionDialog(
requireContext(),
onItemSelected = { storageIndex ->
if (viewModel.item.sources.size > 1) {
val dialog = getVideoVersionDialog(
requireContext(),
viewModel.item,
onItemSelected = { sourceIndex ->
createDownloadPreparingDialog()
viewModel.download(sourceIndex, storageIndex)
},
onCancel = {
binding.itemActions.progressDownload.isVisible = false
binding.itemActions.downloadButton.setIconResource(CoreR.drawable.ic_download)
},
)
dialog.show()
return@getStorageSelectionDialog
}
createDownloadPreparingDialog()
viewModel.download(storageIndex = storageIndex)
},
onCancel = {
binding.itemActions.progressDownload.isVisible = false
binding.itemActions.downloadButton.setIconResource(CoreR.drawable.ic_download)
},
)
storageDialog.show()
return
}
if (viewModel.item.sources.size > 1) {
val dialog = getVideoVersionDialog(
requireContext(),
viewModel.item,
onItemSelected = { sourceIndex ->
createDownloadPreparingDialog()
viewModel.download(sourceIndex)
},
onCancel = {
binding.itemActions.progressDownload.isVisible = false
binding.itemActions.downloadButton.setIconResource(CoreR.drawable.ic_download)
},
)
dialog.show()
return
}
createDownloadPreparingDialog()
viewModel.download()
}

override fun onResume() {
super.onResume()

Expand Down Expand Up @@ -495,6 +505,31 @@ class MovieFragment : Fragment() {
dialog.show()
}

private fun createPickQualityDialog() {
val qualityEntries = resources.getStringArray(CoreR.array.quality_entries)
val qualityValues = resources.getStringArray(CoreR.array.quality_values)
val quality = appPreferences.downloadQuality
val currentQualityIndex = qualityValues.indexOf(quality)
var selectedQuality = quality


val builder = MaterialAlertDialogBuilder(requireContext())
builder.setTitle("Download Quality")
builder.setSingleChoiceItems(qualityEntries, currentQualityIndex) { _, which ->
selectedQuality = qualityValues[which]
}
builder.setPositiveButton("Download") { dialog, _ ->
appPreferences.downloadQuality = selectedQuality
download()
dialog.dismiss()
}
builder.setNegativeButton("Cancel") { dialog, _ ->
dialog.dismiss()
}
val dialog = builder.create()
dialog.show()
}

private fun navigateToPlayerActivity(
playerItems: Array<PlayerItem>,
) {
Expand Down
Loading
Loading