Skip to content

Commit

Permalink
Merge branch 'show-trailer' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
UweTrottmann committed Nov 17, 2023
2 parents 172df7e + fa7bd05 commit 179fc35
Show file tree
Hide file tree
Showing 10 changed files with 215 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,8 @@
package com.battlelancer.seriesguide.movies.details

import android.content.Context
import com.battlelancer.seriesguide.SgApp
import com.battlelancer.seriesguide.movies.MoviesSettings.getMoviesLanguage
import com.battlelancer.seriesguide.util.Errors
import com.battlelancer.seriesguide.tmdbapi.TmdbTools2
import com.uwetrottmann.androidutils.GenericSimpleLoader
import com.uwetrottmann.tmdb2.entities.Videos
import com.uwetrottmann.tmdb2.enumerations.VideoType
import timber.log.Timber

/**
* Loads a YouTube movie trailer from TMDb. Tries to get a local trailer, if not falls back to
Expand All @@ -20,48 +15,7 @@ class MovieTrailersLoader(context: Context, private val tmdbId: Int) :
GenericSimpleLoader<String?>(context) {

override fun loadInBackground(): String? {
// try to get a local trailer
val trailer = getTrailerVideoId(
getMoviesLanguage(context), "get local movie trailer"
)
if (trailer != null) {
return trailer
}
Timber.d("Did not find a local movie trailer.")

// fall back to default language trailer
return getTrailerVideoId(null, "get default movie trailer")
}

private fun getTrailerVideoId(language: String?, action: String): String? {
val moviesService = SgApp.getServicesComponent(context).moviesService()
try {
val response = moviesService.videos(tmdbId, language).execute()
if (response.isSuccessful) {
return extractTrailer(response.body())
} else {
Errors.logAndReport(action, response)
}
} catch (e: Exception) {
Errors.logAndReport(action, e)
}
return null
return TmdbTools2().getMovieTrailerYoutubeId(context, tmdbId)
}

private fun extractTrailer(videos: Videos?): String? {
val results = videos?.results
if (results == null || results.size == 0) {
return null
}

// Pick the first YouTube trailer
for (video in results) {
val videoId = video.key
if (video.type == VideoType.TRAILER && "YouTube" == video.site
&& !videoId.isNullOrEmpty()) {
return videoId
}
}
return null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ class ShowFragment() : Fragment() {
val buttonShortcut: Button
val buttonLanguage: Button
val buttonRate: View
val buttonTrailer: Button
val buttonSimilar: Button
val buttonImdb: Button
val buttonShowMetacritic: Button
Expand Down Expand Up @@ -140,6 +141,7 @@ class ShowFragment() : Fragment() {
buttonShortcut = view.findViewById(R.id.buttonShowShortcut)
buttonLanguage = view.findViewById(R.id.buttonShowLanguage)
buttonRate = view.findViewById(R.id.containerRatings)
buttonTrailer = view.findViewById(R.id.buttonShowTrailer)
buttonSimilar = view.findViewById(R.id.buttonShowSimilar)
buttonImdb = view.findViewById(R.id.buttonShowImdb)
buttonShowMetacritic = view.findViewById(R.id.buttonShowMetacritic)
Expand Down Expand Up @@ -406,6 +408,19 @@ class ShowFragment() : Fragment() {
binding.textViewRatingVotes.text = showForUi.traktVotes
binding.textViewRatingUser.text = showForUi.traktUserRating

// Trailer button
binding.buttonTrailer.apply {
if (showForUi.trailerVideoId != null) {
setOnClickListener {
ServiceUtils.openYoutube(showForUi.trailerVideoId, requireContext())
}
isEnabled = true
} else {
setOnClickListener(null)
isEnabled = false
}
}

// Similar shows button.
binding.buttonSimilar.setOnClickListener {
show.tmdbId?.also {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.battlelancer.seriesguide.util.LanguageTools
import com.battlelancer.seriesguide.util.TextTools
import com.battlelancer.seriesguide.util.TimeTools
import com.battlelancer.seriesguide.util.Utils
import com.github.michaelbull.result.onSuccess
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
Expand All @@ -45,7 +46,8 @@ class ShowViewModel(application: Application) : AndroidViewModel(application) {
val genres: String,
val traktRating: String,
val traktVotes: String,
val traktUserRating: String
val traktUserRating: String,
val trailerVideoId: String?
)

// Mediator to compute some additional data for the UI in the background.
Expand Down Expand Up @@ -99,20 +101,38 @@ class ShowViewModel(application: Application) : AndroidViewModel(application) {
val traktUserRating =
TraktTools.buildUserRatingString(application, show.ratingUser)

val databaseValues = ShowForUi(
show,
timeOrNull,
baseInfo,
overviewStyled,
languageData,
country,
releaseYear,
lastUpdated,
genres,
traktRating, traktVotes, traktUserRating,
null
)

withContext(Dispatchers.Main) {
showForUi.value =
ShowForUi(
show,
timeOrNull,
baseInfo,
overviewStyled,
languageData,
country,
releaseYear,
lastUpdated,
genres,
traktRating, traktVotes, traktUserRating
)
showForUi.value = databaseValues
}

// Do network request after returning data from the database
val showTmdbId = show.tmdbId
if (showTmdbId != null && languageData != null) {
TmdbTools2().getShowTrailerYoutubeId(
application,
show.tmdbId,
languageData.languageCode
).onSuccess {
if (it != null) {
withContext(Dispatchers.Main) {
showForUi.value = databaseValues.copy(trailerVideoId = it)
}
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import com.battlelancer.seriesguide.ui.OverviewActivity
import com.battlelancer.seriesguide.ui.dialogs.L10nDialogFragment
import com.battlelancer.seriesguide.util.ImageTools
import com.battlelancer.seriesguide.util.LanguageTools
import com.battlelancer.seriesguide.util.ServiceUtils
import com.battlelancer.seriesguide.util.TextTools
import com.battlelancer.seriesguide.util.TimeTools
import com.battlelancer.seriesguide.util.ViewTools
Expand Down Expand Up @@ -152,6 +153,17 @@ class AddShowDialogFragment : AppCompatDialogFragment() {
this.binding?.textViewAddDescription?.isGone = false
populateShowViews(show)
}
model.trailer.observe(this) { videoId ->
this.binding?.buttonAddTrailer?.apply {
if (videoId != null) {
setOnClickListener { ServiceUtils.openYoutube(videoId, requireContext()) }
isEnabled = true
} else {
setOnClickListener(null)
isEnabled = false
}
}
}
model.watchProvider.observe(this) { watchInfo ->
this.binding?.buttonAddStreamingSearch?.let {
val providerInfo = StreamingSearch.configureButton(it, watchInfo, false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.battlelancer.seriesguide.SgApp
import com.battlelancer.seriesguide.shows.database.SgShow2
import com.battlelancer.seriesguide.shows.tools.GetShowTools.GetShowError.GetShowDoesNotExist
import com.battlelancer.seriesguide.streaming.StreamingSearch
import com.battlelancer.seriesguide.tmdbapi.TmdbTools2
import com.github.michaelbull.result.onFailure
import com.github.michaelbull.result.onSuccess
import kotlinx.coroutines.Dispatchers
Expand All @@ -35,6 +36,7 @@ class AddShowDialogViewModel(

val languageCode = MutableLiveData<String>()
val showDetails: LiveData<ShowDetails>
val trailer: LiveData<String?>

init {
// Set original value for region.
Expand Down Expand Up @@ -66,6 +68,12 @@ class AddShowDialogViewModel(
}
}
}
this.trailer = languageCode.switchMap { languageCode ->
liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
TmdbTools2().getShowTrailerYoutubeId(application, showTmdbId, languageCode)
.onSuccess { emit(it) }
}
}
}

private val watchInfoMediator = MediatorLiveData<StreamingSearch.WatchInfo>().apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package com.battlelancer.seriesguide.tmdbapi

import android.content.Context
import com.battlelancer.seriesguide.SgApp
import com.battlelancer.seriesguide.movies.MoviesSettings
import com.battlelancer.seriesguide.provider.SgRoomDatabase
import com.battlelancer.seriesguide.util.Errors
import com.battlelancer.seriesguide.util.isRetryError
Expand All @@ -26,16 +27,19 @@ import com.uwetrottmann.tmdb2.entities.TmdbDate
import com.uwetrottmann.tmdb2.entities.TvEpisode
import com.uwetrottmann.tmdb2.entities.TvShow
import com.uwetrottmann.tmdb2.entities.TvShowResultsPage
import com.uwetrottmann.tmdb2.entities.Videos
import com.uwetrottmann.tmdb2.entities.WatchProviders
import com.uwetrottmann.tmdb2.enumerations.AppendToResponseItem
import com.uwetrottmann.tmdb2.enumerations.ExternalSource
import com.uwetrottmann.tmdb2.enumerations.SortBy
import com.uwetrottmann.tmdb2.enumerations.VideoType
import com.uwetrottmann.tmdb2.services.PeopleService
import com.uwetrottmann.tmdb2.services.TvEpisodesService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import retrofit2.awaitResponse
import retrofit2.create
import timber.log.Timber
import java.util.Calendar
import java.util.Date

Expand Down Expand Up @@ -224,6 +228,95 @@ class TmdbTools2 {
return null
}

fun getShowTrailerYoutubeId(
context: Context,
showTmdbId: Int,
languageCode: String
): Result<String?, TmdbError> {
val action = "get show trailer"
val tmdb = SgApp.getServicesComponent(context.applicationContext).tmdb()
return runCatching {
tmdb.tvService()
.videos(showTmdbId, languageCode)
.execute()
}.mapError {
Errors.logAndReport(action, it)
if (it.isRetryError()) TmdbRetry else TmdbStop
}.andThen {
if (it.isSuccessful) {
val results = it.body()?.results
if (results != null) {
return@andThen Ok(extractTrailer(it.body()))
} else {
Errors.logAndReport(action, it, "results is null")
}
} else {
Errors.logAndReport(action, it)
}
return@andThen Err(TmdbStop)
}
}

/**
* Loads a YouTube movie trailer from TMDb. Tries to get a local trailer, if not falls back to
* English.
*/
fun getMovieTrailerYoutubeId(
context: Context,
movieTmdbId: Int
): String? {
// try to get a local trailer
val trailer = getMovieTrailerYoutubeId(
context, movieTmdbId, MoviesSettings.getMoviesLanguage(context), "get local movie trailer"
)
if (trailer != null) {
return trailer
}
Timber.d("Did not find a local movie trailer.")

// fall back to default language trailer
return getMovieTrailerYoutubeId(
context, movieTmdbId, null, "get default movie trailer"
)
}

private fun getMovieTrailerYoutubeId(
context: Context,
movieTmdbId: Int,
languageCode: String?,
action: String
): String? {
val moviesService = SgApp.getServicesComponent(context).moviesService()
try {
val response = moviesService.videos(movieTmdbId, languageCode).execute()
if (response.isSuccessful) {
return extractTrailer(response.body())
} else {
Errors.logAndReport(action, response)
}
} catch (e: Exception) {
Errors.logAndReport(action, e)
}
return null
}

private fun extractTrailer(videos: Videos?): String? {
val results = videos?.results
if (results == null || results.size == 0) {
return null
}

// Pick the first YouTube trailer
for (video in results) {
val videoId = video.key
if (video.type == VideoType.TRAILER && "YouTube" == video.site
&& !videoId.isNullOrEmpty()) {
return videoId
}
}
return null
}

suspend fun getShowWatchProviders(
tmdb: Tmdb,
language: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ object LanguageTools {
return null
}

data class LanguageData(val languageCode: String?, val languageString: String)
data class LanguageData(val languageCode: String, val languageString: String)

/**
* Based on the first two letters gets the language display name. Except for
Expand Down
Loading

0 comments on commit 179fc35

Please sign in to comment.