6.11.3 commit

This commit is contained in:
Xilin Jia 2024-10-16 18:55:47 +01:00
parent 61ce79f667
commit a38d7766fc
18 changed files with 187 additions and 237 deletions

View File

@ -31,8 +31,8 @@ android {
testApplicationId "ac.mdiq.podcini.tests" testApplicationId "ac.mdiq.podcini.tests"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
versionCode 3020272 versionCode 3020273
versionName "6.11.2" versionName "6.11.3"
applicationId "ac.mdiq.podcini.R" applicationId "ac.mdiq.podcini.R"
def commit = "" def commit = ""

View File

@ -54,7 +54,6 @@ class FeedBuilder(val context: Context, val showError: (String?, String)->Unit)
val eList: MutableList<Episode> = mutableListOf() val eList: MutableList<Episode> = mutableListOf()
val uURL = URL(url) val uURL = URL(url)
// if (url.startsWith("https://youtube.com/playlist?") || url.startsWith("https://music.youtube.com/playlist?")) {
if (uURL.path.startsWith("/playlist") || uURL.path.startsWith("/playlist")) { if (uURL.path.startsWith("/playlist") || uURL.path.startsWith("/playlist")) {
val playlistInfo = PlaylistInfo.getInfo(Vista.getService(0), url) ?: return@launch val playlistInfo = PlaylistInfo.getInfo(Vista.getService(0), url) ?: return@launch
feed_.title = playlistInfo.name feed_.title = playlistInfo.name

View File

@ -1436,10 +1436,10 @@ class PlaybackService : MediaLibraryService() {
Logd(TAG, "setDataSource1 audioStreamsList ${audioStreamsList.size}") Logd(TAG, "setDataSource1 audioStreamsList ${audioStreamsList.size}")
val audioIndex = if (isNetworkRestricted && prefLowQualityMedia) 0 else audioStreamsList.size - 1 val audioIndex = if (isNetworkRestricted && prefLowQualityMedia) 0 else audioStreamsList.size - 1
val audioStream = audioStreamsList[audioIndex] val audioStream = audioStreamsList[audioIndex]
Logd(TAG, "setDataSource1 use audio quality: ${audioStream.bitrate}") Logd(TAG, "setDataSource1 use audio quality: ${audioStream.bitrate} forceVideo: ${media.forceVideo}")
val aSource = DefaultMediaSourceFactory(context).createMediaSource( val aSource = DefaultMediaSourceFactory(context).createMediaSource(
MediaItem.Builder().setMediaMetadata(metadata).setTag(metadata).setUri(Uri.parse(audioStream.content)).build()) MediaItem.Builder().setMediaMetadata(metadata).setTag(metadata).setUri(Uri.parse(audioStream.content)).build())
if (media.episode?.feed?.preferences?.videoModePolicy != VideoMode.AUDIO_ONLY) { if (media.forceVideo || media.episode?.feed?.preferences?.videoModePolicy != VideoMode.AUDIO_ONLY) {
Logd(TAG, "setDataSource1 result: $streamInfo") Logd(TAG, "setDataSource1 result: $streamInfo")
Logd(TAG, "setDataSource1 videoStreams: ${streamInfo.videoStreams.size} videoOnlyStreams: ${streamInfo.videoOnlyStreams.size} audioStreams: ${streamInfo.audioStreams.size}") Logd(TAG, "setDataSource1 videoStreams: ${streamInfo.videoStreams.size} videoOnlyStreams: ${streamInfo.videoOnlyStreams.size} audioStreams: ${streamInfo.audioStreams.size}")
val videoStreamsList = getSortedStreamVideosList(streamInfo.videoStreams, streamInfo.videoOnlyStreams, true, true) val videoStreamsList = getSortedStreamVideosList(streamInfo.videoStreams, streamInfo.videoOnlyStreams, true, true)
@ -2502,12 +2502,10 @@ class PlaybackService : MediaLibraryService() {
private fun setToFallbackSpeed(speed: Float) { private fun setToFallbackSpeed(speed: Float) {
if (playbackService?.mPlayer == null || playbackService!!.isSpeedForward) return if (playbackService?.mPlayer == null || playbackService!!.isSpeedForward) return
if (!playbackService!!.isFallbackSpeed) { if (!playbackService!!.isFallbackSpeed) {
playbackService!!.normalSpeed = playbackService!!.mPlayer!!.getPlaybackSpeed() playbackService!!.normalSpeed = playbackService!!.mPlayer!!.getPlaybackSpeed()
playbackService!!.mPlayer!!.setPlaybackParams(speed, isSkipSilence) playbackService!!.mPlayer!!.setPlaybackParams(speed, isSkipSilence)
} else playbackService!!.mPlayer!!.setPlaybackParams(playbackService!!.normalSpeed, isSkipSilence) } else playbackService!!.mPlayer!!.setPlaybackParams(playbackService!!.normalSpeed, isSkipSilence)
playbackService!!.isFallbackSpeed = !playbackService!!.isFallbackSpeed playbackService!!.isFallbackSpeed = !playbackService!!.isFallbackSpeed
} }
@ -2526,6 +2524,7 @@ class PlaybackService : MediaLibraryService() {
playbackService?.mPlayer?.pause(true, reinit = false) playbackService?.mPlayer?.pause(true, reinit = false)
playbackService?.isSpeedForward = false playbackService?.isSpeedForward = false
playbackService?.isFallbackSpeed = false playbackService?.isFallbackSpeed = false
(curMedia as? EpisodeMedia)?.forceVideo = false
} }
PlayerStatus.PAUSED, PlayerStatus.PREPARED -> { PlayerStatus.PAUSED, PlayerStatus.PREPARED -> {
playbackService?.mPlayer?.resume() playbackService?.mPlayer?.resume()

View File

@ -9,6 +9,9 @@ import ac.mdiq.podcini.util.showStackTrace
import android.content.Context import android.content.Context
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import io.realm.kotlin.ext.isManaged import io.realm.kotlin.ext.isManaged
import io.realm.kotlin.types.EmbeddedRealmObject import io.realm.kotlin.types.EmbeddedRealmObject
import io.realm.kotlin.types.annotations.Ignore import io.realm.kotlin.types.annotations.Ignore
@ -68,6 +71,9 @@ class EpisodeMedia: EmbeddedRealmObject, Playable {
// if null: unknown, will be checked // if null: unknown, will be checked
var hasEmbeddedPicture: Boolean? = null var hasEmbeddedPicture: Boolean? = null
@Ignore
var forceVideo by mutableStateOf(false)
/* Used for loading item when restoring from parcel. */ /* Used for loading item when restoring from parcel. */
// var episodeId: Long = 0 // var episodeId: Long = 0
// private set // private set

View File

@ -82,6 +82,7 @@ abstract class EpisodeActionButton internal constructor(@JvmField var item: Epis
Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp), shape = RoundedCornerShape(16.dp)) { Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp), shape = RoundedCornerShape(16.dp)) {
Row(modifier = Modifier.padding(16.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) { Row(modifier = Modifier.padding(16.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
val label = getLabel() val label = getLabel()
Logd(TAG, "button label: $label")
if (label != R.string.play_label && label != R.string.pause_label && label != R.string.download_label) { if (label != R.string.play_label && label != R.string.pause_label && label != R.string.download_label) {
IconButton(onClick = { IconButton(onClick = {
PlayActionButton(item).onClick(context) PlayActionButton(item).onClick(context)
@ -140,9 +141,9 @@ abstract class EpisodeActionButton internal constructor(@JvmField var item: Epis
fun playVideoIfNeeded(context: Context, media: Playable) { fun playVideoIfNeeded(context: Context, media: Playable) {
val item = (media as? EpisodeMedia)?.episode val item = (media as? EpisodeMedia)?.episode
if (item?.feed?.preferences?.videoModePolicy != VideoMode.AUDIO_ONLY if ((media as? EpisodeMedia)?.forceVideo == true || (item?.feed?.preferences?.videoModePolicy != VideoMode.AUDIO_ONLY
&& videoPlayMode != VideoMode.AUDIO_ONLY.code && videoMode != VideoMode.AUDIO_ONLY && videoPlayMode != VideoMode.AUDIO_ONLY.code && videoMode != VideoMode.AUDIO_ONLY
&& media.getMediaType() == MediaType.VIDEO) && media.getMediaType() == MediaType.VIDEO))
context.startActivity(getPlayerActivityIntent(context, MediaType.VIDEO)) context.startActivity(getPlayerActivityIntent(context, MediaType.VIDEO))
} }
} }
@ -365,7 +366,6 @@ class StreamActionButton(item: Episode) : EpisodeActionButton(item) {
} }
companion object { companion object {
fun stream(context: Context, media: Playable) { fun stream(context: Context, media: Playable) {
if (media !is EpisodeMedia || !InTheatre.isCurMedia(media)) PlaybackService.clearCurTempSpeed() if (media !is EpisodeMedia || !InTheatre.isCurMedia(media)) PlaybackService.clearCurTempSpeed()
PlaybackServiceStarter(context, media).shouldStreamThisTime(true).callEvenIfRunning(true).start() PlaybackServiceStarter(context, media).shouldStreamThisTime(true).callEvenIfRunning(true).start()

View File

@ -138,11 +138,11 @@ class MainActivity : CastEnabledActivity() {
} }
} }
override fun onSlide(view: View, slideOffset: Float) { override fun onSlide(view: View, slideOffset: Float) {
val audioPlayer = supportFragmentManager.findFragmentByTag(AudioPlayerFragment.TAG) as? AudioPlayerFragment ?: return // val audioPlayer = supportFragmentManager.findFragmentByTag(AudioPlayerFragment.TAG) as? AudioPlayerFragment ?: return
// if (slideOffset == 0.0f) { //STATE_COLLAPSED // if (slideOffset == 0.0f) { //STATE_COLLAPSED
// audioPlayer.scrollToTop() // audioPlayer.scrollToTop()
// } // }
audioPlayer.fadePlayerToToolbar(slideOffset) // audioPlayer.fadePlayerToToolbar(slideOffset)
} }
} }
@ -397,10 +397,10 @@ class MainActivity : CastEnabledActivity() {
navigationBarInsets.bottom + (if (visible) externalPlayerHeight else 0)) navigationBarInsets.bottom + (if (visible) externalPlayerHeight else 0))
mainView.layoutParams = params mainView.layoutParams = params
// val playerView = findViewById<FragmentContainerView>(R.id.playerFragment1) // val playerView = findViewById<FragmentContainerView>(R.id.playerFragment1)
val playerView = findViewById<ComposeView>(R.id.player1) // val playerView = findViewById<ComposeView>(R.id.player1)
val playerParams = playerView?.layoutParams as? MarginLayoutParams // val playerParams = playerView?.layoutParams as? MarginLayoutParams
playerParams?.setMargins(navigationBarInsets.left, 0, navigationBarInsets.right, 0) // playerParams?.setMargins(navigationBarInsets.left, 0, navigationBarInsets.right, 0)
playerView?.layoutParams = playerParams // playerView?.layoutParams = playerParams
audioPlayerView.visibility = if (visible) View.VISIBLE else View.GONE audioPlayerView.visibility = if (visible) View.VISIBLE else View.GONE
} }

View File

@ -117,7 +117,7 @@ class ShareReceiverActivity : AppCompatActivity() {
if (finish) activity.finish() if (finish) activity.finish()
} }
// Youtube media // Youtube media
(isYoutubeURL(url) && url.path.startsWith("/watch")) || isYoutubeServiceURL(url) -> { (isYoutubeURL(url) && (url.path.startsWith("/watch") || url.path.startsWith("/live"))) || isYoutubeServiceURL(url) -> {
if (log != null) upsertBlk(log) {it.type = "youtube media" } if (log != null) upsertBlk(log) {it.type = "youtube media" }
Logd(TAG, "got youtube media") Logd(TAG, "got youtube media")
mediaCB() mediaCB()

View File

@ -102,8 +102,11 @@ class VideoplayerActivity : CastEnabledActivity() {
var vmCode = 0 var vmCode = 0
if (curMedia is EpisodeMedia) { if (curMedia is EpisodeMedia) {
val media_ = curMedia as EpisodeMedia val media_ = curMedia as EpisodeMedia
val vPol = media_.episode?.feed?.preferences?.videoModePolicy var vPol = media_.episode?.feed?.preferences?.videoModePolicy
if (vPol != null && vPol != VideoMode.NONE) vmCode = vPol.code if (vPol != null) {
if (vPol == VideoMode.AUDIO_ONLY && media_.forceVideo) vPol = VideoMode.WINDOW_VIEW
if (vPol != VideoMode.NONE) vmCode = vPol.code
}
} }
Logd(TAG, "onCreate vmCode: $vmCode") Logd(TAG, "onCreate vmCode: $vmCode")
if (vmCode == 0) vmCode = videoPlayMode if (vmCode == 0) vmCode = videoPlayMode
@ -290,6 +293,7 @@ class VideoplayerActivity : CastEnabledActivity() {
when (item.itemId) { when (item.itemId) {
R.id.player_switch_to_audio_only -> { R.id.player_switch_to_audio_only -> {
switchToAudioOnly = true switchToAudioOnly = true
(curMedia as? EpisodeMedia)?.forceVideo = false
finish() finish()
return true return true
} }
@ -703,23 +707,23 @@ class VideoplayerActivity : CastEnabledActivity() {
lifecycleScope.launch { lifecycleScope.launch {
try { try {
episode = withContext(Dispatchers.IO) { episode = withContext(Dispatchers.IO) {
var feedItem = (curMedia as? EpisodeMedia)?.episodeOrFetch() var episode_ = (curMedia as? EpisodeMedia)?.episodeOrFetch()
if (feedItem != null) { if (episode_ != null) {
val duration = feedItem.media?.getDuration() ?: Int.MAX_VALUE val duration = episode_.media?.getDuration() ?: Int.MAX_VALUE
val url = feedItem.media?.downloadUrl val url = episode_.media?.downloadUrl
val shownotesCleaner = ShownotesCleaner(requireContext()) val shownotesCleaner = ShownotesCleaner(requireContext())
if (url?.contains("youtube.com") == true && feedItem.description?.startsWith("Short:") == true) { if (url?.contains("youtube.com") == true && episode_.description?.startsWith("Short:") == true) {
Logd(TAG, "getting extended description: ${feedItem.title}") Logd(TAG, "getting extended description: ${episode_.title}")
try { try {
val info = feedItem.streamInfo val info = episode_.streamInfo
if (info?.description?.content != null) { if (info?.description?.content != null) {
feedItem = upsert(feedItem) { it.description = info.description?.content } episode_ = upsert(episode_) { it.description = info.description?.content }
webviewData = shownotesCleaner.processShownotes(info.description!!.content, duration) webviewData = shownotesCleaner.processShownotes(info.description!!.content, duration)
} else webviewData = shownotesCleaner.processShownotes(episode!!.description ?: "", duration) } else webviewData = shownotesCleaner.processShownotes(episode_.description ?: "", duration)
} catch (e: Exception) { Logd(TAG, "StreamInfo error: ${e.message}") } } catch (e: Exception) { Logd(TAG, "StreamInfo error: ${e.message}") }
} else webviewData = shownotesCleaner.processShownotes(episode!!.description ?: "", duration) } else webviewData = shownotesCleaner.processShownotes(episode_.description ?: "", duration)
} }
feedItem episode_
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
Logd(TAG, "load() item ${episode?.id}") Logd(TAG, "load() item ${episode?.id}")
@ -798,6 +802,7 @@ class VideoplayerActivity : CastEnabledActivity() {
binding.toggleViews.setOnClickListener { (activity as VideoplayerActivity).toggleViews() } binding.toggleViews.setOnClickListener { (activity as VideoplayerActivity).toggleViews() }
binding.audioOnly.setOnClickListener { binding.audioOnly.setOnClickListener {
(activity as? VideoplayerActivity)?.switchToAudioOnly = true (activity as? VideoplayerActivity)?.switchToAudioOnly = true
(curMedia as? EpisodeMedia)?.forceVideo = false
(activity as? VideoplayerActivity)?.finish() (activity as? VideoplayerActivity)?.finish()
} }
if (!itemsLoaded) webvDescription?.loadDataWithBaseURL("https://127.0.0.1", webviewData, if (!itemsLoaded) webvDescription?.loadDataWithBaseURL("https://127.0.0.1", webviewData,

View File

@ -679,13 +679,14 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
if (index>=vms.size) return@LaunchedEffect if (index>=vms.size) return@LaunchedEffect
if (isDownloading()) vm.dlPercent = dls?.getProgress(vms[index].episode.media!!.downloadUrl!!) ?: 0 if (isDownloading()) vm.dlPercent = dls?.getProgress(vms[index].episode.media!!.downloadUrl!!) ?: 0
Logd(TAG, "LaunchedEffect $index downloadState: ${vms[index].downloadState} ${vm.episode.media?.downloaded} ${vm.dlPercent}") Logd(TAG, "LaunchedEffect $index downloadState: ${vms[index].downloadState} ${vm.episode.media?.downloaded} ${vm.dlPercent}")
vm.actionButton = EpisodeActionButton.forItem(vms[index].episode) vm.actionButton = EpisodeActionButton.forItem(vm.episode)
vm.actionRes = vm.actionButton!!.getDrawable() vm.actionRes = vm.actionButton!!.getDrawable()
} }
LaunchedEffect(key1 = status) { LaunchedEffect(key1 = status) {
if (index>=vms.size) return@LaunchedEffect if (index>=vms.size) return@LaunchedEffect
Logd(TAG, "LaunchedEffect $index isPlayingState: ${vms[index].isPlayingState} ${vms[index].episode.title}") Logd(TAG, "LaunchedEffect $index isPlayingState: ${vms[index].isPlayingState} ${vms[index].episode.title}")
vm.actionButton = EpisodeActionButton.forItem(vms[index].episode) vm.actionButton = EpisodeActionButton.forItem(vm.episode)
Logd(TAG, "LaunchedEffect vm.actionButton: ${vm.actionButton?.getLabel()}")
vm.actionRes = vm.actionButton!!.getDrawable() vm.actionRes = vm.actionButton!!.getDrawable()
} }
// LaunchedEffect(vm.isPlayingState) { // LaunchedEffect(vm.isPlayingState) {
@ -696,7 +697,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
} }
Box(modifier = Modifier.width(40.dp).height(40.dp).padding(end = 10.dp).align(Alignment.CenterVertically).pointerInput(Unit) { Box(modifier = Modifier.width(40.dp).height(40.dp).padding(end = 10.dp).align(Alignment.CenterVertically).pointerInput(Unit) {
detectTapGestures(onLongPress = { vm.showAltActionsDialog = true }, onTap = { detectTapGestures(onLongPress = { vm.showAltActionsDialog = true }, onTap = {
vm.actionButton?.onClick(activity) vms[index].actionButton?.onClick(activity)
}) })
}, contentAlignment = Alignment.Center) { }, contentAlignment = Alignment.Center) {
// actionRes = actionButton.getDrawable() // actionRes = actionButton.getDrawable()

View File

@ -294,8 +294,7 @@ class SleepTimerDialog : DialogFragment() {
paintDial.strokeWidth = size * 0.01f paintDial.strokeWidth = size * 0.01f
val textPos = radToPoint(i / 24.0f * 360f, size / 2 - 2.5f * padding) val textPos = radToPoint(i / 24.0f * 360f, size / 2 - 2.5f * padding)
paintText.textSize = 0.4f * padding paintText.textSize = 0.4f * padding
canvas.drawText(i.toString(), textPos.x.toFloat(), canvas.drawText(i.toString(), textPos.x.toFloat(), textPos.y + (-paintText.descent() - paintText.ascent()) / 2, paintText)
textPos.y + (-paintText.descent() - paintText.ascent()) / 2, paintText)
} }
val outer = radToPoint(i / 24.0f * 360f, size / 2 - 1.7f * padding) val outer = radToPoint(i / 24.0f * 360f, size / 2 - 1.7f * padding)
val inner = radToPoint(i / 24.0f * 360f, size / 2 - 1.9f * padding) val inner = radToPoint(i / 24.0f * 360f, size / 2 - 1.9f * padding)

View File

@ -1,16 +1,15 @@
package ac.mdiq.podcini.ui.fragment package ac.mdiq.podcini.ui.fragment
import ac.mdiq.podcini.R import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.AudioplayerFragmentBinding
import ac.mdiq.podcini.net.utils.NetworkUtils.fetchHtmlSource import ac.mdiq.podcini.net.utils.NetworkUtils.fetchHtmlSource
import ac.mdiq.podcini.playback.PlaybackServiceStarter import ac.mdiq.podcini.playback.PlaybackServiceStarter
import ac.mdiq.podcini.playback.ServiceStatusHandler import ac.mdiq.podcini.playback.ServiceStatusHandler
import ac.mdiq.podcini.playback.base.InTheatre.curEpisode import ac.mdiq.podcini.playback.base.InTheatre.curEpisode
import ac.mdiq.podcini.playback.base.InTheatre.curMedia import ac.mdiq.podcini.playback.base.InTheatre.curMedia
import ac.mdiq.podcini.playback.base.MediaPlayerBase import ac.mdiq.podcini.playback.base.MediaPlayerBase
import ac.mdiq.podcini.playback.base.MediaPlayerBase.Companion.status
import ac.mdiq.podcini.playback.base.PlayerStatus import ac.mdiq.podcini.playback.base.PlayerStatus
import ac.mdiq.podcini.playback.base.VideoMode import ac.mdiq.podcini.playback.base.VideoMode
import ac.mdiq.podcini.playback.cast.CastEnabledActivity
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.curDurationFB import ac.mdiq.podcini.playback.service.PlaybackService.Companion.curDurationFB
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.curPositionFB import ac.mdiq.podcini.playback.service.PlaybackService.Companion.curPositionFB
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.curSpeedFB import ac.mdiq.podcini.playback.service.PlaybackService.Companion.curSpeedFB
@ -40,7 +39,6 @@ import ac.mdiq.podcini.ui.compose.ChaptersDialog
import ac.mdiq.podcini.ui.compose.ChooseRatingDialog import ac.mdiq.podcini.ui.compose.ChooseRatingDialog
import ac.mdiq.podcini.ui.compose.CustomTheme import ac.mdiq.podcini.ui.compose.CustomTheme
import ac.mdiq.podcini.ui.dialog.* import ac.mdiq.podcini.ui.dialog.*
import ac.mdiq.podcini.ui.fragment.EpisodeInfoFragment.Companion
import ac.mdiq.podcini.ui.utils.ShownotesCleaner import ac.mdiq.podcini.ui.utils.ShownotesCleaner
import ac.mdiq.podcini.ui.view.ShownotesWebView import ac.mdiq.podcini.ui.view.ShownotesWebView
import ac.mdiq.podcini.util.EventFlow import ac.mdiq.podcini.util.EventFlow
@ -52,9 +50,11 @@ import android.content.*
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.* import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.widget.Toolbar
import androidx.compose.foundation.* import androidx.compose.foundation.*
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@ -64,11 +64,13 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.zIndex
import androidx.core.app.ShareCompat import androidx.core.app.ShareCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
@ -77,7 +79,6 @@ import androidx.lifecycle.lifecycleScope
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.session.MediaController import androidx.media3.session.MediaController
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -90,15 +91,9 @@ import org.apache.commons.lang3.StringUtils
import java.text.DecimalFormat import java.text.DecimalFormat
import java.text.NumberFormat import java.text.NumberFormat
import kotlin.math.max import kotlin.math.max
import kotlin.math.min
@UnstableApi @UnstableApi
class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener { class AudioPlayerFragment : Fragment() {
var _binding: AudioplayerFragmentBinding? = null
private val binding get() = _binding!!
private lateinit var toolbar: MaterialToolbar
private var showPlayer1 by mutableStateOf(true)
private var isCollapsed by mutableStateOf(true) private var isCollapsed by mutableStateOf(true)
// private lateinit var controllerFuture: ListenableFuture<MediaController> // private lateinit var controllerFuture: ListenableFuture<MediaController>
@ -122,6 +117,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private var duration by mutableIntStateOf(0) private var duration by mutableIntStateOf(0)
private var txtvLengtTexth by mutableStateOf("") private var txtvLengtTexth by mutableStateOf("")
private var sliderValue by mutableFloatStateOf(0f) private var sliderValue by mutableFloatStateOf(0f)
private var sleepTimerActive by mutableStateOf(isSleepTimerActive())
private var shownotesCleaner: ShownotesCleaner? = null private var shownotesCleaner: ShownotesCleaner? = null
@ -145,48 +141,41 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
super.onCreateView(inflater, container, savedInstanceState) super.onCreateView(inflater, container, savedInstanceState)
_binding = AudioplayerFragmentBinding.inflate(inflater)
binding.root.setOnTouchListener { _: View?, _: MotionEvent? -> true } // Avoid clicks going through player to fragments below
Logd(TAG, "fragment onCreateView") Logd(TAG, "fragment onCreateView")
toolbar = binding.toolbar
toolbar.title = ""
toolbar.setNavigationOnClickListener {
val bottomSheet = (activity as MainActivity).bottomSheet
bottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED
}
toolbar.setOnMenuItemClickListener(this)
controller = createHandler() controller = createHandler()
controller!!.init() controller!!.init()
onCollaped() onCollaped()
binding.player1.setContent { val composeView = ComposeView(requireContext()).apply {
setContent {
CustomTheme(requireContext()) { CustomTheme(requireContext()) {
if (showPlayer1) PlayerUI() // Column(modifier = Modifier.fillMaxSize().statusBarsPadding().navigationBarsPadding() ) {
else Spacer(modifier = Modifier.size(0.dp)) // if (isCollapsed) PlayerUI()
//// else Spacer(modifier = Modifier.size(0.dp))
// Toolbar()
// DetailUI(modifier = Modifier.weight(1f))
// if (!isCollapsed) PlayerUI()
//// else Spacer(modifier = Modifier.size(0.dp))
// }
Box(modifier = Modifier.fillMaxWidth().statusBarsPadding().navigationBarsPadding()) {
val aligm = if (isCollapsed) Alignment.TopCenter else Alignment.BottomCenter
PlayerUI(Modifier.align(aligm).zIndex(1f))
if (!isCollapsed) {
Column(Modifier.padding(bottom = 90.dp)) {
Toolbar()
DetailUI(modifier = Modifier)
} }
} }
binding.composeDetailView.setContent {
CustomTheme(requireContext()) {
DetailUI()
// if (!isCollapsed) DetailUI()
// else Spacer(modifier = Modifier.size(0.dp))
} }
} }
binding.player2.setContent {
CustomTheme(requireContext()) {
if (!showPlayer1) PlayerUI()
else Spacer(modifier = Modifier.size(0.dp))
} }
} }
// cardViewSeek = binding.cardViewSeek
(activity as MainActivity).setPlayerVisible(false) (activity as MainActivity).setPlayerVisible(false)
return binding.root return composeView
} }
override fun onDestroyView() { override fun onDestroyView() {
Logd(TAG, "Fragment destroyed") Logd(TAG, "Fragment destroyed")
_binding = null
controller?.release() controller?.release()
controller = null controller = null
// MediaController.releaseFuture(controllerFuture) // MediaController.releaseFuture(controllerFuture)
@ -195,9 +184,9 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun PlayerUI() { fun PlayerUI(modifier: Modifier) {
Column(modifier = Modifier.fillMaxWidth()) {
val textColor = MaterialTheme.colorScheme.onSurface val textColor = MaterialTheme.colorScheme.onSurface
Column(modifier = modifier.fillMaxWidth().background(MaterialTheme.colorScheme.surface)) {
Text(titleText, maxLines = 1, color = textColor, style = MaterialTheme.typography.bodyMedium) Text(titleText, maxLines = 1, color = textColor, style = MaterialTheme.typography.bodyMedium)
Slider(value = sliderValue, valueRange = 0f..duration.toFloat(), Slider(value = sliderValue, valueRange = 0f..duration.toFloat(),
// colors = SliderDefaults.colors( // colors = SliderDefaults.colors(
@ -231,7 +220,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
if (playbackService == null) PlaybackServiceStarter(requireContext(), curMedia!!).start() if (playbackService == null) PlaybackServiceStarter(requireContext(), curMedia!!).start()
} }
AsyncImage(model = imgLoc, contentDescription = "imgvCover", placeholder = painterResource(R.mipmap.ic_launcher), error = painterResource(R.mipmap.ic_launcher), AsyncImage(model = imgLoc, contentDescription = "imgvCover", placeholder = painterResource(R.mipmap.ic_launcher), error = painterResource(R.mipmap.ic_launcher),
modifier = Modifier.width(70.dp).height(70.dp).padding(start = 5.dp) modifier = Modifier.width(65.dp).height(65.dp).padding(start = 5.dp)
.clickable(onClick = { .clickable(onClick = {
Logd(TAG, "playerUiFragment icon was clicked") Logd(TAG, "playerUiFragment icon was clicked")
if (isCollapsed) { if (isCollapsed) {
@ -255,7 +244,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
Icon(painter = painterResource(R.drawable.ic_playback_speed), tint = textColor, Icon(painter = painterResource(R.drawable.ic_playback_speed), tint = textColor,
contentDescription = "speed", contentDescription = "speed",
modifier = Modifier.width(48.dp).height(48.dp).clickable(onClick = { modifier = Modifier.width(43.dp).height(43.dp).clickable(onClick = {
VariableSpeedDialog.newInstance(booleanArrayOf(true, true, true), null)?.show(childFragmentManager, null) VariableSpeedDialog.newInstance(booleanArrayOf(true, true, true), null)?.show(childFragmentManager, null)
})) }))
Text(txtvPlaybackSpeed, color = textColor, style = MaterialTheme.typography.bodySmall) Text(txtvPlaybackSpeed, color = textColor, style = MaterialTheme.typography.bodySmall)
@ -264,7 +253,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
Icon(painter = painterResource(R.drawable.ic_fast_rewind), tint = textColor, Icon(painter = painterResource(R.drawable.ic_fast_rewind), tint = textColor,
contentDescription = "rewind", contentDescription = "rewind",
modifier = Modifier.width(48.dp).height(48.dp).combinedClickable(onClick = { modifier = Modifier.width(43.dp).height(43.dp).combinedClickable(onClick = {
if (controller != null && playbackService?.isServiceReady() == true) { if (controller != null && playbackService?.isServiceReady() == true) {
playbackService?.mPlayer?.seekDelta(-UserPreferences.rewindSecs * 1000) playbackService?.mPlayer?.seekDelta(-UserPreferences.rewindSecs * 1000)
} }
@ -296,7 +285,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
Icon(painter = painterResource(R.drawable.ic_fast_forward), tint = textColor, Icon(painter = painterResource(R.drawable.ic_fast_forward), tint = textColor,
contentDescription = "forward", contentDescription = "forward",
modifier = Modifier.width(48.dp).height(48.dp).combinedClickable(onClick = { modifier = Modifier.width(43.dp).height(43.dp).combinedClickable(onClick = {
if (controller != null && playbackService?.isServiceReady() == true) { if (controller != null && playbackService?.isServiceReady() == true) {
playbackService?.mPlayer?.seekDelta(UserPreferences.fastForwardSecs * 1000) playbackService?.mPlayer?.seekDelta(UserPreferences.fastForwardSecs * 1000)
} }
@ -317,7 +306,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
} }
Icon(painter = painterResource(R.drawable.ic_skip_48dp), tint = textColor, Icon(painter = painterResource(R.drawable.ic_skip_48dp), tint = textColor,
contentDescription = "rewind", contentDescription = "rewind",
modifier = Modifier.width(48.dp).height(48.dp).combinedClickable(onClick = { modifier = Modifier.width(43.dp).height(43.dp).combinedClickable(onClick = {
if (controller != null && MediaPlayerBase.status == PlayerStatus.PLAYING) { if (controller != null && MediaPlayerBase.status == PlayerStatus.PLAYING) {
val speedForward = UserPreferences.speedforwardSpeed val speedForward = UserPreferences.speedforwardSpeed
if (speedForward > 0.1f) speedForward(speedForward) if (speedForward > 0.1f) speedForward(speedForward)
@ -332,9 +321,72 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
} }
} }
@Composable
fun Toolbar() {
val media: Playable = curMedia ?: return
val feedItem = if (media is EpisodeMedia) media.episodeOrFetch() else null
val textColor = MaterialTheme.colorScheme.onSurface
val mediaType = curMedia?.getMediaType()
val notAudioOnly = (curMedia as? EpisodeMedia)?.episode?.feed?.preferences?.videoModePolicy != VideoMode.AUDIO_ONLY
Row(modifier = Modifier.fillMaxWidth().padding(10.dp), horizontalArrangement = Arrangement.SpaceBetween) {
Icon(painter = painterResource(R.drawable.ic_arrow_down), tint = textColor, contentDescription = "Collapse", modifier = Modifier.clickable {
(activity as MainActivity).bottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED)
})
var homeIcon by remember { mutableIntStateOf(R.drawable.baseline_home_24)}
Icon(painter = painterResource(homeIcon), tint = textColor, contentDescription = "Home", modifier = Modifier.clickable {
homeIcon = if (showHomeText) R.drawable.ic_home else R.drawable.outline_home_24
buildHomeReaderText()
})
if (mediaType == MediaType.VIDEO) Icon(painter = painterResource(R.drawable.baseline_fullscreen_24), tint = textColor, contentDescription = "Play video",
modifier = Modifier.clickable {
if (notAudioOnly || (curMedia as? EpisodeMedia)?.forceVideo == true) {
// playPause()
} else {
(curMedia as? EpisodeMedia)?.forceVideo = true
status = PlayerStatus.STOPPED
playbackService?.mPlayer?.pause(true, reinit = true)
playbackService?.recreateMediaPlayer()
}
VideoPlayerActivityStarter(requireContext()).start()
})
if (controller != null) {
val sleepRes = if (sleepTimerActive) R.drawable.ic_sleep_off else R.drawable.ic_sleep
Icon(painter = painterResource(sleepRes), tint = textColor, contentDescription = "Sleep timer", modifier = Modifier.clickable {
SleepTimerDialog().show(childFragmentManager, "SleepTimerDialog")
})
}
if (currentMedia is EpisodeMedia) Icon(painter = painterResource(R.drawable.ic_feed), tint = textColor, contentDescription = "Open podcast",
modifier = Modifier.clickable {
if (feedItem?.feedId != null) {
val intent: Intent = MainActivity.getIntentToOpenFeed(requireContext(), feedItem.feedId!!)
startActivity(intent)
}
})
Icon(painter = painterResource(R.drawable.ic_share), tint = textColor, contentDescription = "Share", modifier = Modifier.clickable {
if (currentItem != null) {
val shareDialog: ShareDialog = ShareDialog.newInstance(currentItem!!)
shareDialog.show((requireActivity().supportFragmentManager), "ShareEpisodeDialog")
}
})
Icon(painter = painterResource(R.drawable.baseline_offline_share_24), tint = textColor, contentDescription = "Share Note", modifier = Modifier.clickable {
val notes = if (showHomeText) readerhtml else feedItem?.description
if (!notes.isNullOrEmpty()) {
val shareText = HtmlCompat.fromHtml(notes, HtmlCompat.FROM_HTML_MODE_COMPACT).toString()
val context = requireContext()
val intent = ShareCompat.IntentBuilder(context)
.setType("text/plain")
.setText(shareText)
.setChooserTitle(R.string.share_notes_label)
.createChooserIntent()
context.startActivity(intent)
}
})
}
}
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun DetailUI() { fun DetailUI(modifier: Modifier) {
var showChooseRatingDialog by remember { mutableStateOf(false) } var showChooseRatingDialog by remember { mutableStateOf(false) }
if (showChooseRatingDialog) ChooseRatingDialog(listOf(currentItem!!)) { if (showChooseRatingDialog) ChooseRatingDialog(listOf(currentItem!!)) {
showChooseRatingDialog = false showChooseRatingDialog = false
@ -343,7 +395,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
if (showChaptersDialog) ChaptersDialog(media = currentMedia!!, onDismissRequest = {showChaptersDialog = false}) if (showChaptersDialog) ChaptersDialog(media = currentMedia!!, onDismissRequest = {showChaptersDialog = false})
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
Column(modifier = Modifier.fillMaxWidth().verticalScroll(scrollState)) { Column(modifier = modifier.fillMaxWidth().verticalScroll(scrollState)) {
val textColor = MaterialTheme.colorScheme.onSurface val textColor = MaterialTheme.colorScheme.onSurface
fun copyText(text: String): Boolean { fun copyText(text: String): Boolean {
val clipboardManager: ClipboardManager? = ContextCompat.getSystemService(requireContext(), ClipboardManager::class.java) val clipboardManager: ClipboardManager? = ContextCompat.getSystemService(requireContext(), ClipboardManager::class.java)
@ -647,7 +699,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
// if (isCollapsed) { // if (isCollapsed) {
isCollapsed = false isCollapsed = false
if (shownotesCleaner == null) shownotesCleaner = ShownotesCleaner(requireContext()) if (shownotesCleaner == null) shownotesCleaner = ShownotesCleaner(requireContext())
showPlayer1 = false // showPlayer1 = false
if (currentMedia != null) updateUi(currentMedia!!) if (currentMedia != null) updateUi(currentMedia!!)
setIsShowPlay(isShowPlay) setIsShowPlay(isShowPlay)
updateDetails() updateDetails()
@ -657,7 +709,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
fun onCollaped() { fun onCollaped() {
Logd(TAG, "onCollaped()") Logd(TAG, "onCollaped()")
isCollapsed = true isCollapsed = true
showPlayer1 = true // showPlayer1 = true
if (currentMedia != null) updateUi(currentMedia!!) if (currentMedia != null) updateUi(currentMedia!!)
setIsShowPlay(isShowPlay) setIsShowPlay(isShowPlay)
} }
@ -703,7 +755,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
val item = (currentMedia as? EpisodeMedia)?.episodeOrFetch() val item = (currentMedia as? EpisodeMedia)?.episodeOrFetch()
if (item != null) setItem(item) if (item != null) setItem(item)
setChapterDividers() setChapterDividers()
setupOptionsMenu() sleepTimerActive = isSleepTimerActive()
if (currentMedia != null) updateUi(currentMedia!!) if (currentMedia != null) updateUi(currentMedia!!)
// TODO: disable for now // TODO: disable for now
// if (!includingChapters) loadMediaInfo(true) // if (!includingChapters) loadMediaInfo(true)
@ -850,7 +902,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
is FlowEvent.RatingEvent -> onRatingEvent(event) is FlowEvent.RatingEvent -> onRatingEvent(event)
is FlowEvent.PlayerErrorEvent -> MediaPlayerErrorDialog.show(activity as Activity, event) is FlowEvent.PlayerErrorEvent -> MediaPlayerErrorDialog.show(activity as Activity, event)
// is FlowEvent.SleepTimerUpdatedEvent -> if (event.isCancelled || event.wasJustEnabled()) loadMediaInfo(false) // is FlowEvent.SleepTimerUpdatedEvent -> if (event.isCancelled || event.wasJustEnabled()) loadMediaInfo(false)
is FlowEvent.SleepTimerUpdatedEvent -> if (event.isCancelled || event.wasJustEnabled()) setupOptionsMenu() is FlowEvent.SleepTimerUpdatedEvent -> if (event.isCancelled || event.wasJustEnabled()) sleepTimerActive = isSleepTimerActive()
is FlowEvent.PlaybackPositionEvent -> onPlaybackPositionEvent(event) is FlowEvent.PlaybackPositionEvent -> onPlaybackPositionEvent(event)
is FlowEvent.SpeedChangedEvent -> updatePlaybackSpeedButton(event) is FlowEvent.SpeedChangedEvent -> updatePlaybackSpeedButton(event)
else -> {} else -> {}
@ -866,87 +918,11 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
} }
} }
private fun setupOptionsMenu() {
if (toolbar.menu.size() == 0) toolbar.inflateMenu(R.menu.mediaplayer)
val isEpisodeMedia = currentMedia is EpisodeMedia
toolbar.menu?.findItem(R.id.open_feed_item)?.setVisible(isEpisodeMedia)
// val item = if (isEpisodeMedia) (currentMedia as EpisodeMedia).episodeOrFetch() else null
// EpisodeMenuHandler.onPrepareMenu(toolbar.menu, item)
val mediaType = curMedia?.getMediaType()
val notAudioOnly = (curMedia as? EpisodeMedia)?.episode?.feed?.preferences?.videoModePolicy != VideoMode.AUDIO_ONLY
toolbar.menu?.findItem(R.id.show_video)?.setVisible(mediaType == MediaType.VIDEO && notAudioOnly)
if (controller != null) {
toolbar.menu.findItem(R.id.set_sleeptimer_item).setVisible(!isSleepTimerActive())
toolbar.menu.findItem(R.id.disable_sleeptimer_item).setVisible(isSleepTimerActive())
}
(activity as? CastEnabledActivity)?.requestCastButton(toolbar.menu)
}
override fun onMenuItemClick(menuItem: MenuItem): Boolean {
val media: Playable = curMedia ?: return false
val feedItem = if (media is EpisodeMedia) media.episodeOrFetch() else null
// if (feedItem != null && EpisodeMenuHandler.onMenuItemClicked(this, menuItem.itemId, feedItem)) return true
val itemId = menuItem.itemId
when (itemId) {
R.id.show_home_reader_view -> {
if (showHomeText) menuItem.setIcon(R.drawable.ic_home)
else menuItem.setIcon(R.drawable.outline_home_24)
buildHomeReaderText()
}
R.id.show_video -> {
playPause()
VideoPlayerActivityStarter(requireContext()).start()
}
R.id.disable_sleeptimer_item, R.id.set_sleeptimer_item -> SleepTimerDialog().show(childFragmentManager, "SleepTimerDialog")
R.id.open_feed_item -> {
if (feedItem?.feedId != null) {
val intent: Intent = MainActivity.getIntentToOpenFeed(requireContext(), feedItem.feedId!!)
startActivity(intent)
}
}
R.id.share_notes -> {
val notes = if (showHomeText) readerhtml else feedItem?.description
if (!notes.isNullOrEmpty()) {
val shareText = HtmlCompat.fromHtml(notes, HtmlCompat.FROM_HTML_MODE_COMPACT).toString()
val context = requireContext()
val intent = ShareCompat.IntentBuilder(context)
.setType("text/plain")
.setText(shareText)
.setChooserTitle(R.string.share_notes_label)
.createChooserIntent()
context.startActivity(intent)
}
}
R.id.share_item -> {
if (currentItem != null) {
val shareDialog: ShareDialog = ShareDialog.newInstance(currentItem!!)
shareDialog.show((requireActivity().supportFragmentManager), "ShareEpisodeDialog")
}
}
else -> return false
}
return true
}
// fun scrollToTop() { // fun scrollToTop() {
//// binding.itemDescriptionFragment.scrollTo(0, 0) //// binding.itemDescriptionFragment.scrollTo(0, 0)
// savePreference() // savePreference()
// } // }
fun fadePlayerToToolbar(slideOffset: Float) {
val playerFadeProgress = (max(0.0, min(0.2, (slideOffset - 0.2f).toDouble())) / 0.2f).toFloat()
val player = binding.player1
player.alpha = 1 - playerFadeProgress
player.visibility = if (playerFadeProgress > 0.99f) View.GONE else View.VISIBLE
val toolbarFadeProgress = (max(0.0, min(0.2, (slideOffset - 0.6f).toDouble())) / 0.2f).toFloat()
toolbar.setAlpha(toolbarFadeProgress)
toolbar.visibility = if (toolbarFadeProgress < 0.01f) View.GONE else View.VISIBLE
}
companion object { companion object {
val TAG = AudioPlayerFragment::class.simpleName ?: "Anonymous" val TAG = AudioPlayerFragment::class.simpleName ?: "Anonymous"
var media3Controller: MediaController? = null var media3Controller: MediaController? = null

View File

@ -32,6 +32,8 @@ import ac.mdiq.podcini.storage.utils.DurationConverter
import ac.mdiq.podcini.storage.utils.ImageResourceUtils import ac.mdiq.podcini.storage.utils.ImageResourceUtils
import ac.mdiq.podcini.ui.actions.* import ac.mdiq.podcini.ui.actions.*
import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.activity.VideoplayerActivity
import ac.mdiq.podcini.ui.activity.VideoplayerActivity.Companion
import ac.mdiq.podcini.ui.compose.ChaptersDialog import ac.mdiq.podcini.ui.compose.ChaptersDialog
import ac.mdiq.podcini.ui.compose.ChooseRatingDialog import ac.mdiq.podcini.ui.compose.ChooseRatingDialog
import ac.mdiq.podcini.ui.compose.CustomTheme import ac.mdiq.podcini.ui.compose.CustomTheme
@ -456,26 +458,6 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
else -> txtvSize = "" else -> txtvSize = ""
} }
// val imgLocFB = ImageResourceUtils.getFallbackImageLocation(episode!!)
// val imageLoader = imgvCover.context.imageLoader
// val imageRequest = ImageRequest.Builder(requireContext())
// .data(episode!!.imageLocation)
// .placeholder(R.color.light_gray)
// .listener(object : ImageRequest.Listener {
// override fun onError(request: ImageRequest, result: ErrorResult) {
// val fallbackImageRequest = ImageRequest.Builder(requireContext())
// .data(imgLocFB)
// .setHeader("User-Agent", "Mozilla/5.0")
// .error(R.mipmap.ic_launcher)
// .target(imgvCover)
// .build()
// imageLoader.enqueue(fallbackImageRequest)
// }
// })
// .target(imgvCover)
// .build()
// imageLoader.enqueue(imageRequest)
updateButtons() updateButtons()
} }

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M14.6,10.26v1.31L17,9.33 14.6,7.1v1.28c-2.33,0.32 -3.26,1.92 -3.6,3.52 0.83,-1.13 1.93,-1.64 3.6,-1.64zM16,23L6,23c-1.1,0 -2,-0.9 -2,-2L4,5h2v16h10v2zM18,1h-8c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2L20,3c0,-1.1 -0.9,-2 -2,-2zM18,16h-8L10,4h8v12z"/>
</vector>

View File

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/audioplayer_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.compose.ui.platform.ComposeView
android:id="@+id/player1"
android:layout_width="match_parent"
android:layout_height="@dimen/external_player_height"
android:elevation="8dp"
android:outlineProvider="none"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/actionBarSize"
android:theme="?attr/actionBarTheme"
app:navigationContentDescription="@string/toolbar_back_button_content_description"
app:navigationIcon="@drawable/ic_arrow_down" />
<androidx.compose.ui.platform.ComposeView
android:id="@+id/composeDetailView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginBottom="12dp"/>
<androidx.compose.ui.platform.ComposeView
android:id="@+id/player2"
android:layout_width="match_parent"
android:layout_height="@dimen/external_player_height"/>
</LinearLayout>
</LinearLayout>

View File

@ -7,8 +7,8 @@ buildscript {
ext.kotlin_version = "$libs.versions.kotlin" ext.kotlin_version = "$libs.versions.kotlin"
dependencies { dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.android.tools.build:gradle:8.5.2' classpath libs.gradle
classpath 'org.codehaus.groovy:groovy-xml:3.0.19' classpath libs.groovy.xml
} }
} }

View File

@ -1,3 +1,14 @@
# 6.11.3
* supports Youtube live episodes received from share
* fixed info not showing when playing video in window mode
* AudioPlayer is fully in Compose, fixed the issue of top menu sometimes not shown
* if you have podcast set to AudioOnly, you can tap on the square icon on the top bar of PlayerDetailed to force play video
* this will re-construct the media item for the current episode to include video and plays audio-video together
* it continues this way even after you close the video view and only listen
* during this mode, you can switch between video and audio and the play is uninterrupted
* it will resume playing audio only when you switch episodes and comeback to it
# 6.11.2 # 6.11.2
* fixed PlayerDetailed view not showing full info on Youtube media * fixed PlayerDetailed view not showing full info on Youtube media

View File

@ -0,0 +1,10 @@
Version 6.11.3
* supports Youtube live episodes received from share
* fixed info not showing when playing video in window mode
* AudioPlayer is fully in Compose, fixed the issue of top menu sometimes not shown
* if you have podcast set to AudioOnly, you can tap on the square icon on the top bar of PlayerDetailed to force play video
* this will re-construct the media item for the current episode to include video and plays audio-video together
* it continues this way even after you close the video view and only listen
* during this mode, you can switch between video and audio and the play is uninterrupted
* it will resume playing audio only when you switch episodes and comeback to it

View File

@ -22,7 +22,9 @@ fontawesomeTypeface = "5.13.3.0-kotlin"
fyydlin = "v0.5.0" fyydlin = "v0.5.0"
googleMaterialTypeface = "4.0.0.3-kotlin" googleMaterialTypeface = "4.0.0.3-kotlin"
googleMaterialTypefaceOutlined = "4.0.0.2-kotlin" googleMaterialTypefaceOutlined = "4.0.0.2-kotlin"
gradle = "8.5.2"
gridlayout = "1.0.0" gridlayout = "1.0.0"
groovyXml = "3.0.19"
iconicsCore = "5.5.0-b01" iconicsCore = "5.5.0-b01"
iconicsViews = "5.5.0-b01" iconicsViews = "5.5.0-b01"
javaxInject = "1" javaxInject = "1"
@ -123,6 +125,8 @@ fontawesome-typeface = { module = "com.mikepenz:fontawesome-typeface", version.r
fyydlin = { module = "com.github.mfietz:fyydlin", version.ref = "fyydlin" } fyydlin = { module = "com.github.mfietz:fyydlin", version.ref = "fyydlin" }
google-material-typeface-outlined = { module = "com.mikepenz:google-material-typeface-outlined", version.ref = "googleMaterialTypefaceOutlined" } google-material-typeface-outlined = { module = "com.mikepenz:google-material-typeface-outlined", version.ref = "googleMaterialTypefaceOutlined" }
google-material-typeface = { module = "com.mikepenz:google-material-typeface", version.ref = "googleMaterialTypeface" } google-material-typeface = { module = "com.mikepenz:google-material-typeface", version.ref = "googleMaterialTypeface" }
gradle = { module = "com.android.tools.build:gradle", version.ref = "gradle" }
groovy-xml = { module = "org.codehaus.groovy:groovy-xml", version.ref = "groovyXml" }
iconics-views = { module = "com.mikepenz:iconics-views", version.ref = "iconicsViews" } iconics-views = { module = "com.mikepenz:iconics-views", version.ref = "iconicsViews" }
iconics-core = { module = "com.mikepenz:iconics-core", version.ref = "iconicsCore" } iconics-core = { module = "com.mikepenz:iconics-core", version.ref = "iconicsCore" }
javax-inject = { module = "javax.inject:javax.inject", version.ref = "javaxInject" } javax-inject = { module = "javax.inject:javax.inject", version.ref = "javaxInject" }