6.11.3 commit
This commit is contained in:
parent
61ce79f667
commit
a38d7766fc
|
@ -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 = ""
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
CustomTheme(requireContext()) {
|
setContent {
|
||||||
if (showPlayer1) PlayerUI()
|
CustomTheme(requireContext()) {
|
||||||
else Spacer(modifier = Modifier.size(0.dp))
|
// Column(modifier = Modifier.fillMaxSize().statusBarsPadding().navigationBarsPadding() ) {
|
||||||
|
// 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
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
11
changelog.md
11
changelog.md
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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" }
|
||||||
|
|
Loading…
Reference in New Issue