mirror of
https://github.com/Michatec/Radio.git
synced 2026-01-31 07:20:40 +00:00
- Update the libary gson. - Deleted some unuseful Code. - added some locals to locales config.
251 lines
8.9 KiB
Kotlin
251 lines
8.9 KiB
Kotlin
/*
|
|
* SearchResultAdapter.kt
|
|
* Implements the SearchResultAdapter class
|
|
* A SearchResultAdapter is a custom adapter providing search result views for a RecyclerView
|
|
*
|
|
* This file is part of
|
|
* TRANSISTOR - Radio App for Android
|
|
*
|
|
* Copyright (c) 2015-22 - Y20K.org
|
|
* Licensed under the MIT-License
|
|
* http://opensource.org/licenses/MIT
|
|
*/
|
|
|
|
|
|
package com.michatec.radio.search
|
|
|
|
import android.content.Context
|
|
import android.media.AudioAttributes
|
|
import android.media.AudioFocusRequest
|
|
import android.media.AudioManager
|
|
import android.os.Build
|
|
import android.view.LayoutInflater
|
|
import android.view.View
|
|
import android.view.ViewGroup
|
|
import android.widget.Toast
|
|
import androidx.appcompat.app.AppCompatActivity
|
|
import androidx.lifecycle.DefaultLifecycleObserver
|
|
import androidx.lifecycle.LifecycleOwner
|
|
import androidx.media3.common.MediaItem
|
|
import androidx.media3.exoplayer.ExoPlayer
|
|
import androidx.recyclerview.widget.RecyclerView
|
|
import com.google.android.material.textview.MaterialTextView
|
|
import com.michatec.radio.R
|
|
import com.michatec.radio.core.Station
|
|
|
|
|
|
/*
|
|
* SearchResultAdapter class
|
|
*/
|
|
class SearchResultAdapter(
|
|
private val listener: SearchResultAdapterListener,
|
|
var searchResults: List<Station>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
|
|
|
/* Main class variables */
|
|
private var selectedPosition: Int = RecyclerView.NO_POSITION
|
|
private var exoPlayer: ExoPlayer? = null
|
|
private var paused: Boolean = false
|
|
private var isItemSelected: Boolean = false
|
|
|
|
/* Listener Interface */
|
|
interface SearchResultAdapterListener {
|
|
fun onSearchResultTapped(result: Station)
|
|
fun activateAddButton()
|
|
fun deactivateAddButton()
|
|
}
|
|
|
|
|
|
init {
|
|
setHasStableIds(true)
|
|
}
|
|
|
|
|
|
/* Overrides onCreateViewHolder from RecyclerView.Adapter */
|
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
|
val v = LayoutInflater.from(parent.context)
|
|
.inflate(R.layout.element_search_result, parent, false)
|
|
return SearchResultViewHolder(v)
|
|
}
|
|
|
|
|
|
/* Overrides getItemCount from RecyclerView.Adapter */
|
|
override fun getItemCount(): Int {
|
|
return searchResults.size
|
|
}
|
|
|
|
|
|
/* Overrides getItemCount from RecyclerView.Adapter */
|
|
override fun getItemId(position: Int): Long = position.toLong()
|
|
|
|
|
|
/* Overrides onBindViewHolder from RecyclerView.Adapter */
|
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
|
// get reference to ViewHolder
|
|
val searchResultViewHolder: SearchResultViewHolder = holder as SearchResultViewHolder
|
|
val searchResult: Station = searchResults[position]
|
|
|
|
// update text
|
|
searchResultViewHolder.nameView.text = searchResult.name
|
|
searchResultViewHolder.streamView.text = searchResult.getStreamUri()
|
|
|
|
if (searchResult.codec.isNotEmpty()) {
|
|
if (searchResult.bitrate == 0) {
|
|
// show only the codec when the bitrate is at "0" from radio-browser.info API
|
|
searchResultViewHolder.bitrateView.text = searchResult.codec
|
|
} else {
|
|
// show the bitrate and codec if the result is available in the radio-browser.info API
|
|
searchResultViewHolder.bitrateView.text = buildString {
|
|
append(searchResult.codec)
|
|
append(" | ")
|
|
append(searchResult.bitrate)
|
|
append("kbps")}
|
|
}
|
|
} else {
|
|
// do not show for M3U and PLS playlists as they do not include codec or bitrate
|
|
searchResultViewHolder.bitrateView.visibility = View.GONE
|
|
}
|
|
|
|
// mark selected if necessary
|
|
val isSelected = selectedPosition == holder.adapterPosition
|
|
searchResultViewHolder.searchResultLayout.isSelected = isSelected
|
|
|
|
// toggle text scrolling (marquee) if necessary
|
|
searchResultViewHolder.nameView.isSelected = isSelected
|
|
searchResultViewHolder.streamView.isSelected = isSelected
|
|
|
|
// reduce the shadow left and right because of scrolling (Marquee)
|
|
searchResultViewHolder.nameView.setFadingEdgeLength(10)
|
|
searchResultViewHolder.streamView.setFadingEdgeLength(10)
|
|
|
|
// attach touch listener
|
|
searchResultViewHolder.searchResultLayout.setOnClickListener {
|
|
// move marked position
|
|
val previousSelectedPosition = selectedPosition
|
|
selectedPosition = holder.adapterPosition
|
|
notifyItemChanged(previousSelectedPosition)
|
|
notifyItemChanged(selectedPosition)
|
|
|
|
// check if the selected position is the same as before
|
|
val samePositionSelected = previousSelectedPosition == selectedPosition
|
|
|
|
if (samePositionSelected) {
|
|
// if the same position is selected again, reset the selection
|
|
resetSelection(false)
|
|
} else {
|
|
// get the selected station from searchResults
|
|
val selectedStation = searchResults[holder.adapterPosition]
|
|
// perform pre-playback here
|
|
performPrePlayback(searchResultViewHolder.searchResultLayout.context, selectedStation.getStreamUri())
|
|
// hand over station
|
|
listener.onSearchResultTapped(searchResult)
|
|
}
|
|
|
|
// update isItemSelected based on the selection
|
|
isItemSelected = !samePositionSelected
|
|
|
|
// enable/disable the Add button based on isItemSelected
|
|
if (isItemSelected) {
|
|
listener.activateAddButton()
|
|
} else {
|
|
listener.deactivateAddButton()
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private fun performPrePlayback(context: Context, streamUri: String) {
|
|
if (streamUri.contains(".m3u8")) {
|
|
// release previous player if it exists
|
|
stopPrePlayback()
|
|
|
|
// show toast when no playback is possible
|
|
Toast.makeText(context, R.string.toastmessage_preview_playback_failed, Toast.LENGTH_SHORT).show()
|
|
} else {
|
|
stopRadioPlayback(context)
|
|
|
|
// release previous player if it exists
|
|
stopPrePlayback()
|
|
|
|
// create a new instance of ExoPlayer
|
|
exoPlayer = ExoPlayer.Builder(context).build()
|
|
|
|
// create a MediaItem with the streamUri
|
|
val mediaItem = MediaItem.fromUri(streamUri)
|
|
|
|
// set the MediaItem to the ExoPlayer
|
|
exoPlayer?.setMediaItem(mediaItem)
|
|
|
|
// prepare and start the ExoPlayer
|
|
exoPlayer?.prepare()
|
|
exoPlayer?.play()
|
|
|
|
// show toast when playback is possible
|
|
Toast.makeText(context, R.string.toastmessage_preview_playback_started, Toast.LENGTH_SHORT).show()
|
|
|
|
// listen for app pause events
|
|
val lifecycle = (context as AppCompatActivity).lifecycle
|
|
val lifecycleObserver = object : DefaultLifecycleObserver {
|
|
override fun onPause(owner: LifecycleOwner) {
|
|
if (!paused) {
|
|
paused = true
|
|
stopPrePlayback()
|
|
}
|
|
}
|
|
}
|
|
lifecycle.addObserver(lifecycleObserver)
|
|
}
|
|
}
|
|
|
|
|
|
fun stopPrePlayback() {
|
|
// stop the ExoPlayer and release resources
|
|
exoPlayer?.stop()
|
|
exoPlayer?.release()
|
|
exoPlayer = null
|
|
}
|
|
|
|
|
|
private fun stopRadioPlayback(context: Context) {
|
|
// stop radio playback when one is active
|
|
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
|
|
val audioAttributes = AudioAttributes.Builder()
|
|
.setUsage(AudioAttributes.USAGE_MEDIA)
|
|
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
|
.build()
|
|
|
|
val focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
|
|
.setAudioAttributes(audioAttributes)
|
|
.build()
|
|
|
|
audioManager.requestAudioFocus(focusRequest)
|
|
}
|
|
|
|
|
|
/* Resets the selected position */
|
|
fun resetSelection(clearAdapter: Boolean) {
|
|
val currentlySelected: Int = selectedPosition
|
|
selectedPosition = RecyclerView.NO_POSITION
|
|
if (clearAdapter) {
|
|
val previousItemCount = itemCount
|
|
searchResults = emptyList()
|
|
notifyItemRangeRemoved(0, previousItemCount)
|
|
} else {
|
|
notifyItemChanged(currentlySelected)
|
|
stopPrePlayback()
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Inner class: ViewHolder for a radio station search result
|
|
*/
|
|
private inner class SearchResultViewHolder(var searchResultLayout: View) :
|
|
RecyclerView.ViewHolder(searchResultLayout) {
|
|
val nameView: MaterialTextView = searchResultLayout.findViewById(R.id.station_name)
|
|
val streamView: MaterialTextView = searchResultLayout.findViewById(R.id.station_url)
|
|
val bitrateView: MaterialTextView = searchResultLayout.findViewById(R.id.station_bitrate)
|
|
}
|
|
|
|
}
|