mirror of
https://github.com/NiciDieNase/chaosflix
synced 2024-11-10 06:44:17 +00:00
Leanback: split DetailsFragment into separate Fragments for Events and Streams
This commit is contained in:
parent
4e8d5fb371
commit
c2be295029
12 changed files with 370 additions and 104 deletions
|
@ -130,6 +130,7 @@ dependencies {
|
|||
api "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
|
||||
api "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||
|
||||
api "androidx.core:core-ktx:1.2.0"
|
||||
|
||||
api 'androidx.recyclerview:recyclerview:1.1.0'
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package de.nicidienase.chaosflix.common
|
|||
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.Event
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.Recording
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.streaming.Stream
|
||||
|
||||
object ChaosflixUtil {
|
||||
fun getOptimalRecording(recordings: List<Recording>, originalLanguage: String): Recording {
|
||||
|
@ -28,6 +29,14 @@ object ChaosflixUtil {
|
|||
}
|
||||
}
|
||||
|
||||
fun getStringForRecording(recording: Recording): String {
|
||||
return "${if (recording.isHighQuality) "HD" else "SD"} ${recording.folder} [${recording.language}]"
|
||||
}
|
||||
|
||||
fun getStringForStream(stream: Stream): String {
|
||||
return "${stream.display}"
|
||||
}
|
||||
|
||||
private fun getRecordingForGroup(group: List<Recording>?, language: String): Recording {
|
||||
if (group.isNullOrEmpty()) {
|
||||
error("Got empty or null list, this should not happen!")
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
package de.nicidienase.chaosflix.common.util
|
||||
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.Recording
|
||||
|
||||
object RecordingUtil {
|
||||
fun getStringForRecording(recording: Recording): String {
|
||||
return "${if (recording.isHighQuality) "HD" else "SD"} ${recording.folder} [${recording.language}]"
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ class BrowseErrorFragment : ErrorSupportFragment() {
|
|||
}
|
||||
spinnerFragment = SpinnerFragment()
|
||||
spinnerFragment?.let {
|
||||
fragmentManager?.beginTransaction()?.add(fragmentId, it)?.commit()
|
||||
parentFragmentManager?.beginTransaction()?.add(fragmentId, it)?.commit()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,19 +26,19 @@ class BrowseErrorFragment : ErrorSupportFragment() {
|
|||
setErrorContent(resources.getString(resourceId))
|
||||
}
|
||||
|
||||
fun setErrorContent(message: String, fragmentManager: androidx.fragment.app.FragmentManager? = activity?.supportFragmentManager) {
|
||||
fun setErrorContent(message: String, parentFragmentManager: androidx.fragment.app.FragmentManager? = activity?.supportFragmentManager) {
|
||||
try {
|
||||
if (!isDetached) {
|
||||
spinnerFragment?.let {
|
||||
fragmentManager?.beginTransaction()?.remove(it)?.commit()
|
||||
parentFragmentManager?.beginTransaction()?.remove(it)?.commit()
|
||||
}
|
||||
imageDrawable = resources.getDrawable(R.drawable.lb_ic_sad_cloud, null)
|
||||
setMessage(message)
|
||||
setDefaultBackground(TRANSLUCENT)
|
||||
buttonText = resources.getString(R.string.dismiss_error)
|
||||
|
||||
if (fragmentManager != null) {
|
||||
setButtonClickListener { _ -> dismiss(fragmentManager) }
|
||||
if (parentFragmentManager != null) {
|
||||
setButtonClickListener { _ -> dismiss(parentFragmentManager) }
|
||||
} else {
|
||||
setButtonClickListener { _ -> dismiss() }
|
||||
}
|
||||
|
@ -51,22 +51,22 @@ class BrowseErrorFragment : ErrorSupportFragment() {
|
|||
override fun onPause() {
|
||||
super.onPause()
|
||||
spinnerFragment?.let {
|
||||
fragmentManager?.beginTransaction()?.remove(it)?.commit()
|
||||
parentFragmentManager?.beginTransaction()?.remove(it)?.commit()
|
||||
} ?: Log.e(TAG, "Could not remove spinnerFragment")
|
||||
}
|
||||
|
||||
fun dismiss(fragmentManager: androidx.fragment.app.FragmentManager? = activity?.supportFragmentManager) {
|
||||
if (fragmentManager != null) {
|
||||
with(fragmentManager.beginTransaction()) {
|
||||
fun dismiss(parentFragmentManager: androidx.fragment.app.FragmentManager? = activity?.supportFragmentManager) {
|
||||
if (parentFragmentManager != null) {
|
||||
with(parentFragmentManager.beginTransaction()) {
|
||||
spinnerFragment?.let {
|
||||
remove(it)
|
||||
}
|
||||
remove(this@BrowseErrorFragment)
|
||||
commit()
|
||||
fragmentManager.popBackStack()
|
||||
parentFragmentManager.popBackStack()
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "Cannot dismiss, fragmentManager is null")
|
||||
Log.e(TAG, "Cannot dismiss, parentFragmentManager is null")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import androidx.leanback.widget.ListRowPresenter
|
|||
import androidx.leanback.widget.Row
|
||||
import androidx.leanback.widget.SectionRow
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import de.nicidienase.chaosflix.common.mediadata.MediaRepository
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.Conference
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.ConferenceGroup
|
||||
|
@ -65,7 +65,7 @@ class ConferencesBrowseFragment : BrowseSupportFragment() {
|
|||
title = resources.getString(R.string.app_name)
|
||||
badgeDrawable = resources.getDrawable(R.drawable.chaosflix_icon, null)
|
||||
|
||||
viewModel = ViewModelProviders.of(this, ViewModelFactory.getInstance(requireContext())).get(BrowseViewModel::class.java)
|
||||
viewModel = ViewModelProvider(this, ViewModelFactory.getInstance(requireContext())).get(BrowseViewModel::class.java)
|
||||
|
||||
// Recomendation Rows and Adapter
|
||||
watchListAdapter = ChaosflixEventAdapter(eventPresenter)
|
||||
|
@ -102,7 +102,7 @@ class ConferencesBrowseFragment : BrowseSupportFragment() {
|
|||
viewModel.getConferenceGroups().observe(viewLifecycleOwner, Observer { conferenceGroups ->
|
||||
if (conferenceGroups != null && conferenceGroups.isNotEmpty()) {
|
||||
val conferenceRows = ArrayList<Row>()
|
||||
errorFragment?.dismiss(fragmentManager)
|
||||
errorFragment?.dismiss(parentFragmentManager)
|
||||
errorFragment = null
|
||||
for (group in conferenceGroups.sorted()) {
|
||||
var row = conferencesGroupRows.get(group.name)
|
||||
|
@ -122,16 +122,16 @@ class ConferencesBrowseFragment : BrowseSupportFragment() {
|
|||
when (downloaderEvent?.state) {
|
||||
MediaRepository.State.RUNNING -> {
|
||||
Log.i(TAG, "Refresh running")
|
||||
fragmentManager?.let {
|
||||
parentFragmentManager?.let {
|
||||
errorFragment = BrowseErrorFragment.showErrorFragment(it, R.id.browse_fragment)
|
||||
}
|
||||
}
|
||||
MediaRepository.State.DONE -> {
|
||||
if (downloaderEvent.error != null) {
|
||||
val errorMessage = downloaderEvent.error ?: "Error refreshing events"
|
||||
errorFragment?.setErrorContent(errorMessage, fragmentManager)
|
||||
errorFragment?.setErrorContent(errorMessage, parentFragmentManager)
|
||||
} else {
|
||||
errorFragment?.dismiss(fragmentManager)
|
||||
errorFragment?.dismiss(parentFragmentManager)
|
||||
errorFragment = null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.WindowManager
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.core.os.bundleOf
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.Event
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.streaming.Room
|
||||
import de.nicidienase.chaosflix.leanback.R
|
||||
|
@ -20,6 +20,16 @@ class DetailsActivity : androidx.fragment.app.FragmentActivity() {
|
|||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_event_details)
|
||||
val fragment = when(intent.getIntExtra(TYPE, 0)) {
|
||||
TYPE_RECORDING -> EventDetailsFragment().apply {
|
||||
arguments = bundleOf(EVENT to intent.getParcelableExtra<Event>(EVENT) )
|
||||
}
|
||||
TYPE_STREAM -> StreamDetailsFragment().apply {
|
||||
arguments = bundleOf(ROOM to intent.getParcelableExtra<Room>(ROOM))
|
||||
}
|
||||
else -> error("undefinded type")
|
||||
}
|
||||
supportFragmentManager.beginTransaction().replace(R.id.details_fragment_container, fragment).commit()
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
}
|
||||
|
||||
|
|
|
@ -52,11 +52,10 @@ import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
|
|||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory
|
||||
import com.google.android.exoplayer2.upstream.HttpDataSource
|
||||
import com.google.android.exoplayer2.util.Util
|
||||
import de.nicidienase.chaosflix.common.ChaosflixUtil
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.Event
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.Recording
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.streaming.Room
|
||||
import de.nicidienase.chaosflix.common.mediadata.network.ApiFactory
|
||||
import de.nicidienase.chaosflix.common.util.RecordingUtil
|
||||
import de.nicidienase.chaosflix.common.viewmodel.DetailsViewModel
|
||||
import de.nicidienase.chaosflix.common.viewmodel.PlayerViewModel
|
||||
import de.nicidienase.chaosflix.common.viewmodel.ViewModelFactory
|
||||
|
@ -79,8 +78,7 @@ class EventDetailsFragment : DetailsSupportFragment() {
|
|||
private lateinit var detailsViewModel: DetailsViewModel
|
||||
private lateinit var playerViewModel: PlayerViewModel
|
||||
|
||||
private var event: Event? = null
|
||||
private var room: Room? = null
|
||||
private lateinit var event: Event
|
||||
|
||||
private lateinit var rowsAdapter: ArrayObjectAdapter
|
||||
|
||||
|
@ -104,17 +102,8 @@ class EventDetailsFragment : DetailsSupportFragment() {
|
|||
detailsViewModel = ViewModelProvider(this, viewModelFactory).get(DetailsViewModel::class.java)
|
||||
playerViewModel = ViewModelProvider(this, viewModelFactory).get(PlayerViewModel::class.java)
|
||||
|
||||
event = activity?.intent?.getParcelableExtra(DetailsActivity.EVENT)
|
||||
room = activity?.intent?.getParcelableExtra(DetailsActivity.ROOM)
|
||||
|
||||
val eventType =
|
||||
when {
|
||||
event != null -> DetailsActivity.TYPE_RECORDING
|
||||
room != null -> DetailsActivity.TYPE_STREAM
|
||||
else -> -1
|
||||
}
|
||||
|
||||
title = event?.title
|
||||
event = arguments?.getParcelable(DetailsActivity.EVENT) ?: error("Missing Argument Event")
|
||||
title = event.title
|
||||
|
||||
val selector = ClassPresenterSelector()
|
||||
val detailsPresenter = FullWidthDetailsOverviewRowPresenter(
|
||||
|
@ -134,16 +123,13 @@ class EventDetailsFragment : DetailsSupportFragment() {
|
|||
|
||||
detailsBackgroundController.enableParallax()
|
||||
playerGlue = buildPlayerGlue()
|
||||
playerGlue.title = event?.title ?: room?.display
|
||||
playerGlue.subtitle = event?.subtitle ?: ""
|
||||
playerGlue.title = event.title
|
||||
playerGlue.subtitle = event.subtitle ?: ""
|
||||
detailsBackgroundController.setupVideoPlayback(playerGlue)
|
||||
|
||||
onItemViewClickedListener = ItemViewClickedListener(this)
|
||||
|
||||
when (eventType) {
|
||||
DetailsActivity.TYPE_RECORDING -> event?.let { onCreateRecording(it, rowsAdapter) }
|
||||
DetailsActivity.TYPE_STREAM -> room?.let { onCreateStream(it, rowsAdapter) }
|
||||
}
|
||||
onCreateRecording(event, rowsAdapter)
|
||||
adapter = this.rowsAdapter
|
||||
|
||||
startEntranceTransition()
|
||||
|
@ -224,12 +210,12 @@ class EventDetailsFragment : DetailsSupportFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun selectRecordingFromList(event: Event, items: List<Recording>, resultHandler: (Recording) -> Unit) {
|
||||
val onClickListener = DialogInterface.OnClickListener { _, which -> resultHandler.invoke(items[which]) }
|
||||
private fun selectRecordingFromList(event: Event, recordings: List<Recording>, resultHandler: (Recording) -> Unit) {
|
||||
val onClickListener = DialogInterface.OnClickListener { _, which -> resultHandler.invoke(recordings[which]) }
|
||||
if (selectDialog != null) {
|
||||
selectDialog?.dismiss()
|
||||
}
|
||||
val strings = items.map { RecordingUtil.getStringForRecording(it) }.toTypedArray()
|
||||
val strings = recordings.map { ChaosflixUtil.getStringForRecording(it) }.toTypedArray()
|
||||
selectDialog = AlertDialog.Builder(requireContext())
|
||||
.setItems(strings, onClickListener)
|
||||
.setNegativeButton("Autoselect") { _, _ ->
|
||||
|
@ -293,31 +279,9 @@ class EventDetailsFragment : DetailsSupportFragment() {
|
|||
)
|
||||
}
|
||||
|
||||
private fun onCreateStream(room: Room, rowsAdapter: ArrayObjectAdapter) {
|
||||
val detailsOverview = DetailsOverviewRow(room)
|
||||
|
||||
setThumb(room.thumb, detailsOverview)
|
||||
initializeBackgroundWithImage(room.thumb)
|
||||
|
||||
val dashStreams = room.streams.filter { it.slug == "dash-native" }
|
||||
if (dashStreams.isNotEmpty()) { // && detailsViewModel.autoselectStream ) {
|
||||
dashStreams.first().urls["dash"]?.url?.let { preparePlayer(it, "") }
|
||||
}
|
||||
|
||||
val actionAdapter = ArrayObjectAdapter()
|
||||
|
||||
val playAction = Action(ACTION_PLAY, "Play")
|
||||
actionAdapter.add(playAction)
|
||||
|
||||
detailsOverview.actionsAdapter = actionAdapter
|
||||
rowsAdapter.add(detailsOverview)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N || activity?.isInPictureInPictureMode == false) {
|
||||
event?.let { event ->
|
||||
playerViewModel.setPlaybackProgress(event.guid, playerAdapter.currentPosition)
|
||||
}
|
||||
playerViewModel.setPlaybackProgress(event.guid, playerAdapter.currentPosition)
|
||||
playerAdapter.pause()
|
||||
}
|
||||
super.onPause()
|
||||
|
@ -372,7 +336,7 @@ class EventDetailsFragment : DetailsSupportFragment() {
|
|||
private fun buildPlayerGlue(): ChaosMediaPlayerGlue {
|
||||
playerAdapter = LeanbackPlayerAdapter(context, player, 16)
|
||||
return ChaosMediaPlayerGlue(requireContext(), playerAdapter) {
|
||||
event?.guid?.let { detailsViewModel.createBookmark(it) }
|
||||
detailsViewModel.createBookmark(event.guid)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -446,18 +410,18 @@ class EventDetailsFragment : DetailsSupportFragment() {
|
|||
Log.d(TAG, "OnActionClicked")
|
||||
when (action.id) {
|
||||
ACTION_ADD_WATCHLIST -> {
|
||||
event?.guid?.let { detailsViewModel.createBookmark(it) }
|
||||
detailsViewModel.createBookmark(event.guid)
|
||||
val preferences = requireActivity().getSharedPreferences(getString(R.string.watchlist_preferences_key), Context.MODE_PRIVATE)
|
||||
if (preferences.getBoolean(getString(R.string.watchlist_dialog_needed), true)) { // new item
|
||||
showWatchlistInfoDialog(preferences)
|
||||
}
|
||||
}
|
||||
ACTION_REMOVE_WATCHLIST -> {
|
||||
event?.guid?.let { detailsViewModel.removeBookmark(it) }
|
||||
detailsViewModel.removeBookmark(event.guid)
|
||||
}
|
||||
ACTION_PLAY -> {
|
||||
if (player.playbackState == Player.STATE_IDLE) {
|
||||
event?.let { detailsViewModel.play(it) }
|
||||
detailsViewModel.play(event)
|
||||
} else {
|
||||
detailsBackgroundController.switchToVideo()
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ import androidx.fragment.app.FragmentManager
|
|||
import androidx.leanback.app.GuidedStepSupportFragment
|
||||
import androidx.leanback.widget.GuidanceStylist.Guidance
|
||||
import androidx.leanback.widget.GuidedAction
|
||||
import de.nicidienase.chaosflix.common.ChaosflixUtil
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.Recording
|
||||
import de.nicidienase.chaosflix.common.util.RecordingUtil
|
||||
import de.nicidienase.chaosflix.leanback.R
|
||||
|
||||
class RecordingSelectDialog private constructor() : GuidedStepSupportFragment() {
|
||||
|
@ -23,7 +23,7 @@ class RecordingSelectDialog private constructor() : GuidedStepSupportFragment()
|
|||
recordings.forEach {
|
||||
val action = GuidedAction.Builder(requireContext())
|
||||
.id(it.id)
|
||||
.title(RecordingUtil.getStringForRecording(it))
|
||||
.title(ChaosflixUtil.getStringForRecording(it))
|
||||
.build()
|
||||
actions.add(action)
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ class RecordingSelectDialog private constructor() : GuidedStepSupportFragment()
|
|||
}
|
||||
|
||||
fun show(fragmentManager: FragmentManager) {
|
||||
add(fragmentManager, this, R.id.details_fragment)
|
||||
add(fragmentManager, this, R.id.details_fragment_container)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -0,0 +1,303 @@
|
|||
package de.nicidienase.chaosflix.leanback.detail
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.DialogInterface
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.leanback.app.DetailsSupportFragment
|
||||
import androidx.leanback.app.DetailsSupportFragmentBackgroundController
|
||||
import androidx.leanback.widget.Action
|
||||
import androidx.leanback.widget.ArrayObjectAdapter
|
||||
import androidx.leanback.widget.ClassPresenterSelector
|
||||
import androidx.leanback.widget.DetailsOverviewRow
|
||||
import androidx.leanback.widget.FullWidthDetailsOverviewRowPresenter
|
||||
import androidx.leanback.widget.FullWidthDetailsOverviewSharedElementHelper
|
||||
import androidx.leanback.widget.ListRow
|
||||
import androidx.leanback.widget.ListRowPresenter
|
||||
import androidx.leanback.widget.OnActionClickedListener
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.bumptech.glide.request.target.SimpleTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import com.google.android.exoplayer2.C
|
||||
import com.google.android.exoplayer2.ExoPlayerFactory
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer
|
||||
import com.google.android.exoplayer2.ext.leanback.LeanbackPlayerAdapter
|
||||
import com.google.android.exoplayer2.source.ExtractorMediaSource
|
||||
import com.google.android.exoplayer2.source.MediaSource
|
||||
import com.google.android.exoplayer2.source.dash.DashMediaSource
|
||||
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource
|
||||
import com.google.android.exoplayer2.source.hls.HlsMediaSource
|
||||
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
|
||||
import com.google.android.exoplayer2.upstream.DataSource
|
||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
|
||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
|
||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory
|
||||
import com.google.android.exoplayer2.upstream.HttpDataSource
|
||||
import com.google.android.exoplayer2.util.Util
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.streaming.Room
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.streaming.Stream
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.streaming.StreamUrl
|
||||
import de.nicidienase.chaosflix.common.mediadata.network.ApiFactory
|
||||
import de.nicidienase.chaosflix.common.viewmodel.DetailsViewModel
|
||||
import de.nicidienase.chaosflix.common.viewmodel.PlayerViewModel
|
||||
import de.nicidienase.chaosflix.common.viewmodel.ViewModelFactory
|
||||
import de.nicidienase.chaosflix.leanback.ItemViewClickedListener
|
||||
import de.nicidienase.chaosflix.leanback.R
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
class StreamDetailsFragment : DetailsSupportFragment() {
|
||||
|
||||
private var selectDialog: AlertDialog? = null
|
||||
|
||||
private lateinit var detailsViewModel: DetailsViewModel
|
||||
private lateinit var playerViewModel: PlayerViewModel
|
||||
|
||||
private lateinit var room: Room
|
||||
|
||||
private lateinit var rowsAdapter: ArrayObjectAdapter
|
||||
|
||||
private val detailsBackgroundController = DetailsSupportFragmentBackgroundController(this)
|
||||
|
||||
private val playerDelegate = lazy {
|
||||
ExoPlayerFactory.newSimpleInstance(
|
||||
activity,
|
||||
DefaultTrackSelector(
|
||||
AdaptiveTrackSelection.Factory()))
|
||||
}
|
||||
private val player: SimpleExoPlayer by playerDelegate
|
||||
private lateinit var playerAdapter: LeanbackPlayerAdapter
|
||||
private lateinit var playerGlue: ChaosMediaPlayerGlue
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val viewModelFactory = ViewModelFactory.getInstance(requireContext())
|
||||
detailsViewModel = ViewModelProvider(this, viewModelFactory).get(DetailsViewModel::class.java)
|
||||
playerViewModel = ViewModelProvider(this, viewModelFactory).get(PlayerViewModel::class.java)
|
||||
|
||||
room = arguments?.getParcelable(DetailsActivity.ROOM) ?: error("Missing Argument Room")
|
||||
|
||||
val selector = ClassPresenterSelector()
|
||||
val detailsPresenter = FullWidthDetailsOverviewRowPresenter(
|
||||
EventDetailsDescriptionPresenter(requireContext()))
|
||||
|
||||
val helper = FullWidthDetailsOverviewSharedElementHelper()
|
||||
helper.setSharedElementEnterTransition(activity,
|
||||
DetailsActivity.SHARED_ELEMENT_NAME)
|
||||
detailsPresenter.setListener(helper)
|
||||
prepareEntranceTransition()
|
||||
|
||||
detailsPresenter.onActionClickedListener = DetailActionClickedListener()
|
||||
|
||||
selector.addClassPresenter(DetailsOverviewRow::class.java, detailsPresenter)
|
||||
selector.addClassPresenter(ListRow::class.java, ListRowPresenter())
|
||||
rowsAdapter = ArrayObjectAdapter(selector)
|
||||
|
||||
detailsBackgroundController.enableParallax()
|
||||
playerGlue = buildPlayerGlue()
|
||||
playerGlue.title = room.display
|
||||
detailsBackgroundController.setupVideoPlayback(playerGlue)
|
||||
|
||||
onItemViewClickedListener = ItemViewClickedListener(this)
|
||||
|
||||
onCreateStream(room, rowsAdapter)
|
||||
adapter = this.rowsAdapter
|
||||
|
||||
startEntranceTransition()
|
||||
}
|
||||
|
||||
private fun showError(errorMessage: String?) {
|
||||
if (errorMessage != null && errorMessage.isNotBlank()) {
|
||||
view?.let {
|
||||
Toast.makeText(requireContext(), errorMessage, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectStreamFromList(room: Room, streams: List<Stream>, resultHandler: (StreamUrl) -> Unit) {
|
||||
if (selectDialog != null) {
|
||||
selectDialog?.dismiss()
|
||||
}
|
||||
val streamUrls: Map<String, StreamUrl> = streams.flatMap { stream -> stream.urls.map { "${stream.display} ${it.key}" to it.value } }.toMap()
|
||||
val keys = streamUrls.keys.toTypedArray()
|
||||
val onClickListener = DialogInterface.OnClickListener { _, which ->
|
||||
val selectedKey = keys[which]
|
||||
streamUrls[selectedKey]?.let { resultHandler.invoke(it) } ?: Log.e(TAG, "Selected Stream somehow does not exist")
|
||||
}
|
||||
selectDialog = AlertDialog.Builder(requireContext())
|
||||
.setItems(keys, onClickListener)
|
||||
.setNegativeButton("Autoselect") { _, _ ->
|
||||
getAutoselectedStream(room)?.let { resultHandler.invoke(it) }
|
||||
}
|
||||
.setPositiveButton("Always select automatically") { _, _ ->
|
||||
detailsViewModel.autoselectStream = true
|
||||
getAutoselectedStream(room)?.let { resultHandler.invoke(it) }
|
||||
}
|
||||
.create()
|
||||
|
||||
selectDialog?.show()
|
||||
}
|
||||
|
||||
private fun onCreateStream(room: Room, rowsAdapter: ArrayObjectAdapter) {
|
||||
val detailsOverview = DetailsOverviewRow(room)
|
||||
|
||||
setThumb(room.thumb, detailsOverview)
|
||||
initializeBackgroundWithImage(room.thumb)
|
||||
|
||||
val streamUrl = getAutoselectedStream(room)
|
||||
if (detailsViewModel.autoselectStream && streamUrl != null) {
|
||||
detailsBackgroundController.switchToVideo()
|
||||
preparePlayer(streamUrl.url, "")
|
||||
} else {
|
||||
selectStreamFromList(room, room.streams) {
|
||||
detailsBackgroundController.switchToVideo()
|
||||
preparePlayer(it.url)
|
||||
}
|
||||
}
|
||||
|
||||
val actionAdapter = ArrayObjectAdapter()
|
||||
|
||||
val playAction = Action(ACTION_PLAY, "Play")
|
||||
actionAdapter.add(playAction)
|
||||
|
||||
detailsOverview.actionsAdapter = actionAdapter
|
||||
rowsAdapter.add(detailsOverview)
|
||||
}
|
||||
|
||||
private fun getAutoselectedStream(room: Room): StreamUrl? {
|
||||
val dashStreams = room.streams.filter { it.slug == "dash-native" }
|
||||
return dashStreams.first().urls["dash"]
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N || activity?.isInPictureInPictureMode == false) {
|
||||
playerAdapter.pause()
|
||||
}
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
if (playerDelegate.isInitialized()) {
|
||||
player.release()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setThumb(thumbUrl: String, detailsOverview: DetailsOverviewRow) {
|
||||
Glide.with(requireContext())
|
||||
.asBitmap()
|
||||
.load(thumbUrl)
|
||||
.into(object : SimpleTarget<Bitmap>(DETAIL_THUMB_WIDTH, DETAIL_THUMB_HEIGHT) {
|
||||
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
||||
detailsOverview.setImageBitmap(requireContext(), resource)
|
||||
}
|
||||
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
detailsOverview.imageDrawable = ContextCompat.getDrawable(requireContext(), DEFAULT_DRAWABLE)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun initializeBackgroundWithImage(url: String) {
|
||||
detailsBackgroundController.enableParallax()
|
||||
val options = RequestOptions()
|
||||
.fallback(R.drawable.default_background)
|
||||
Glide.with(requireContext())
|
||||
.asBitmap()
|
||||
.load(url)
|
||||
.apply(options)
|
||||
.into(object : SimpleTarget<Bitmap>() {
|
||||
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
||||
detailsBackgroundController.coverBitmap = resource
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun buildPlayerGlue(): ChaosMediaPlayerGlue {
|
||||
playerAdapter = LeanbackPlayerAdapter(context, player, 16)
|
||||
return ChaosMediaPlayerGlue(requireContext(), playerAdapter)
|
||||
}
|
||||
|
||||
private fun preparePlayer(url: String, overrideExtension: String = "") {
|
||||
player.prepare(buildMediaSource(Uri.parse(url), overrideExtension))
|
||||
}
|
||||
|
||||
private fun buildMediaSource(uri: Uri, overrideExtension: String): MediaSource {
|
||||
val mediaDataSourceFactory = buildDataSourceFactory()
|
||||
val type = if (TextUtils.isEmpty(overrideExtension)) {
|
||||
Util.inferContentType(uri)
|
||||
} else
|
||||
Util.inferContentType(".$overrideExtension")
|
||||
when (type) {
|
||||
C.TYPE_DASH -> return DashMediaSource.Factory(
|
||||
DefaultDashChunkSource.Factory(mediaDataSourceFactory),
|
||||
buildDataSourceFactory())
|
||||
.createMediaSource(uri)
|
||||
C.TYPE_HLS -> return HlsMediaSource.Factory(buildDataSourceFactory())
|
||||
.createMediaSource(uri)
|
||||
C.TYPE_SS, C.TYPE_OTHER -> return ExtractorMediaSource.Factory(mediaDataSourceFactory)
|
||||
.createMediaSource(uri)
|
||||
else -> {
|
||||
throw IllegalStateException("Unsupported type: $type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDataSourceFactory(useBandwidthMeter: Boolean = true): DataSource.Factory {
|
||||
return buildDataSourceFactory(if (useBandwidthMeter) BANDWIDTH_METER else null)
|
||||
}
|
||||
|
||||
private fun buildDataSourceFactory(bandwidthMeter: DefaultBandwidthMeter?): DataSource.Factory {
|
||||
return DefaultDataSourceFactory(requireContext(), bandwidthMeter,
|
||||
buildHttpDataSourceFactory(bandwidthMeter))
|
||||
}
|
||||
|
||||
private fun buildHttpDataSourceFactory(bandwidthMeter: DefaultBandwidthMeter?): HttpDataSource.Factory {
|
||||
return DefaultHttpDataSourceFactory(
|
||||
ApiFactory.buildUserAgent(),
|
||||
bandwidthMeter,
|
||||
DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS,
|
||||
DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS,
|
||||
true /* allowCrossProtocolRedirects */)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val BANDWIDTH_METER = DefaultBandwidthMeter()
|
||||
@JvmStatic
|
||||
val TAG = StreamDetailsFragment::class.java.simpleName
|
||||
|
||||
@JvmStatic
|
||||
private val DETAIL_THUMB_WIDTH = 254
|
||||
@JvmStatic
|
||||
private val DETAIL_THUMB_HEIGHT = 143
|
||||
@JvmStatic
|
||||
val DEFAULT_DRAWABLE = R.drawable.default_background
|
||||
|
||||
@JvmStatic
|
||||
private val ACTION_PLAY: Long = 0L
|
||||
}
|
||||
|
||||
private inner class DetailActionClickedListener : OnActionClickedListener {
|
||||
override fun onActionClicked(action: Action) {
|
||||
Log.d(TAG, "OnActionClicked")
|
||||
when (action.id) {
|
||||
ACTION_PLAY -> {
|
||||
detailsBackgroundController.switchToVideo()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -39,18 +39,6 @@ class ChaosflixSettingsFragment : LeanbackSettingsFragment() {
|
|||
} else {
|
||||
setPreferencesFromResource(prefResId, root)
|
||||
}
|
||||
// Disable preferences not relevant for leanback
|
||||
// listOf("allow_metered_networks",
|
||||
// "auto_external_player",
|
||||
// "download_folder",
|
||||
// "delete_data",
|
||||
// "export_favorites",
|
||||
// "import_favorites").forEach {
|
||||
// preferenceManager.findPreference(it).apply {
|
||||
// isVisible = false
|
||||
// isEnabled = false
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<fragment android:id="@+id/details_fragment"
|
||||
android:name="de.nicidienase.chaosflix.leanback.detail.EventDetailsFragment"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".leanback.activities.DetailsActivity"
|
||||
tools:deviceIds="tv"/>
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/details_fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".detail.DetailsActivity"
|
||||
tools:deviceIds="tv"/>
|
|
@ -14,17 +14,17 @@ import androidx.appcompat.app.AppCompatActivity
|
|||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import de.nicidienase.chaosflix.common.ChaosflixUtil
|
||||
import de.nicidienase.chaosflix.common.OfflineItemManager
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.Event
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.Recording
|
||||
import de.nicidienase.chaosflix.common.util.RecordingUtil
|
||||
import de.nicidienase.chaosflix.common.viewmodel.DetailsViewModel
|
||||
import de.nicidienase.chaosflix.common.viewmodel.ViewModelFactory
|
||||
import de.nicidienase.chaosflix.touch.OnEventSelectedListener
|
||||
import de.nicidienase.chaosflix.touch.R
|
||||
import de.nicidienase.chaosflix.touch.browse.cast.CastService
|
||||
import de.nicidienase.chaosflix.touch.playback.PlayerActivity
|
||||
import kotlinx.android.synthetic.main.activity_eventdetails.*
|
||||
import kotlinx.android.synthetic.main.activity_eventdetails.fragment_container
|
||||
|
||||
class EventDetailsActivity : AppCompatActivity(),
|
||||
EventDetailsFragment.OnEventDetailsFragmentInteractionListener,
|
||||
|
@ -140,7 +140,7 @@ class EventDetailsActivity : AppCompatActivity(),
|
|||
}
|
||||
|
||||
private fun selectRecording(event: Event, recordings: List<Recording>, action: (Event, Recording) -> Unit) {
|
||||
val items: List<String> = recordings.map { RecordingUtil.getStringForRecording(it) }
|
||||
val items: List<String> = recordings.map { ChaosflixUtil.getStringForRecording(it) }
|
||||
selectRecordingFromList(items) { i ->
|
||||
action.invoke(event, recordings[i])
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue