4.5.2 commit
This commit is contained in:
parent
1c97bc3334
commit
40ec9deb06
|
@ -21,6 +21,7 @@ Other notable features and changes include:
|
||||||
|
|
||||||
* A more convenient player control displayed on all pages
|
* A more convenient player control displayed on all pages
|
||||||
* A revamped and more efficient expanded player view showing episode description on the front
|
* A revamped and more efficient expanded player view showing episode description on the front
|
||||||
|
* External player class is merged into the player
|
||||||
* New and efficient ways of click and long-click operations on lists:
|
* New and efficient ways of click and long-click operations on lists:
|
||||||
* click on title area opens the podcast/episode
|
* click on title area opens the podcast/episode
|
||||||
* long-press on title area automatically enters in selection mode
|
* long-press on title area automatically enters in selection mode
|
||||||
|
@ -31,11 +32,8 @@ Other notable features and changes include:
|
||||||
* Left and right swipe actions on lists now have telltales and can be configured on the spot
|
* Left and right swipe actions on lists now have telltales and can be configured on the spot
|
||||||
* Played episodes have clearer markings
|
* Played episodes have clearer markings
|
||||||
* Sort dialog no longer dims the main view
|
* Sort dialog no longer dims the main view
|
||||||
* Play speed setting has been straightened up, three places to set the play speed:
|
* Play speed setting has been straightened up, three speed can be set separately or combined: current audio, podcast, and global
|
||||||
* global setting at the preference
|
* customized podcast speed takes precedence when playing an episode
|
||||||
* setting for a feed: either use global or customized
|
|
||||||
* setting at the player: set for current playing and save for global
|
|
||||||
* customized feed setting takes precedence when playing an episode
|
|
||||||
* Added preference "Fast Forward Speed" under "Playback" in settings with default value of 0.0, dialog allows setting a float number (capped between 0.0 and 10.0)
|
* Added preference "Fast Forward Speed" under "Playback" in settings with default value of 0.0, dialog allows setting a float number (capped between 0.0 and 10.0)
|
||||||
* The "Skip to next episode" button on the player
|
* The "Skip to next episode" button on the player
|
||||||
* long-press moves to the next episode
|
* long-press moves to the next episode
|
||||||
|
|
|
@ -149,8 +149,8 @@ android {
|
||||||
// Version code schema (not used):
|
// Version code schema (not used):
|
||||||
// "1.2.3-beta4" -> 1020304
|
// "1.2.3-beta4" -> 1020304
|
||||||
// "1.2.3" -> 1020395
|
// "1.2.3" -> 1020395
|
||||||
versionCode 3020121
|
versionCode 3020122
|
||||||
versionName "4.5.1"
|
versionName "4.5.2"
|
||||||
|
|
||||||
def commit = ""
|
def commit = ""
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -33,6 +33,7 @@ object PlaybackSpeedUtils {
|
||||||
val feed = item.feed
|
val feed = item.feed
|
||||||
if (feed?.preferences != null) {
|
if (feed?.preferences != null) {
|
||||||
playbackSpeed = feed.preferences!!.feedPlaybackSpeed
|
playbackSpeed = feed.preferences!!.feedPlaybackSpeed
|
||||||
|
Log.d(TAG, "using feed speed $playbackSpeed")
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "Can not get feed specific playback speed: $feed")
|
Log.d(TAG, "Can not get feed specific playback speed: $feed")
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,6 +116,10 @@ abstract class PlaybackController(private val activity: FragmentActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isPlaybackServiceReady() : Boolean {
|
||||||
|
return playbackService != null
|
||||||
|
}
|
||||||
|
|
||||||
private fun unbind() {
|
private fun unbind() {
|
||||||
try {
|
try {
|
||||||
activity.unbindService(mConnection)
|
activity.unbindService(mConnection)
|
||||||
|
@ -338,9 +342,9 @@ abstract class PlaybackController(private val activity: FragmentActivity) {
|
||||||
playbackService?.setVideoSurface(holder)
|
playbackService?.setVideoSurface(holder)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setPlaybackSpeed(speed: Float) {
|
fun setPlaybackSpeed(speed: Float, codeArray: Array<Int>? = null) {
|
||||||
if (playbackService != null) {
|
if (playbackService != null) {
|
||||||
playbackService!!.setSpeed(speed)
|
playbackService!!.setSpeed(speed, codeArray)
|
||||||
} else {
|
} else {
|
||||||
EventBus.getDefault().post(SpeedChangedEvent(speed))
|
EventBus.getDefault().post(SpeedChangedEvent(speed))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1617,15 +1617,41 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
val playable: Playable?
|
val playable: Playable?
|
||||||
get() = mediaPlayer?.getPlayable()
|
get() = mediaPlayer?.getPlayable()
|
||||||
|
|
||||||
fun setSpeed(speed: Float) {
|
fun setSpeed(speed: Float, codeArray: Array<Int>? = null) {
|
||||||
isSpeedForward = false
|
isSpeedForward = false
|
||||||
isFallbackSpeed = false
|
isFallbackSpeed = false
|
||||||
|
|
||||||
currentlyPlayingTemporaryPlaybackSpeed = speed
|
currentlyPlayingTemporaryPlaybackSpeed = speed
|
||||||
|
|
||||||
if (currentMediaType == MediaType.VIDEO) {
|
if (currentMediaType == MediaType.VIDEO) {
|
||||||
videoPlaybackSpeed = speed
|
videoPlaybackSpeed = speed
|
||||||
} else {
|
} else {
|
||||||
setPlaybackSpeed(speed)
|
if (codeArray != null && codeArray.size == 3) {
|
||||||
|
if (codeArray[2] == 1) setPlaybackSpeed(speed)
|
||||||
|
if (codeArray[1] == 1 && playable is FeedMedia) {
|
||||||
|
var item = (playable as FeedMedia).item
|
||||||
|
if (item == null) {
|
||||||
|
val itemId = (playable as FeedMedia).itemId
|
||||||
|
item = DBReader.getFeedItem(itemId)
|
||||||
|
}
|
||||||
|
if (item != null) {
|
||||||
|
var feed = item.feed
|
||||||
|
if (feed == null) {
|
||||||
|
feed = DBReader.getFeed(item.feedId)
|
||||||
|
}
|
||||||
|
if (feed != null) {
|
||||||
|
val feedPreferences = feed.preferences
|
||||||
|
if (feedPreferences != null) {
|
||||||
|
feedPreferences.feedPlaybackSpeed = speed
|
||||||
|
Log.d(TAG, "setSpeed ${feed.title} $speed")
|
||||||
|
DBWriter.setFeedPreferences(feedPreferences)
|
||||||
|
EventBus.getDefault().post(
|
||||||
|
SpeedPresetChangedEvent(feedPreferences.feedPlaybackSpeed, feed.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaPlayer?.setPlaybackParams(speed, isSkipSilence)
|
mediaPlayer?.setPlaybackParams(speed, isSkipSilence)
|
||||||
|
|
|
@ -340,7 +340,9 @@ class MainActivity : CastEnabledActivity() {
|
||||||
bottomSheet.peekHeight = playerHeight + navigationBarInsets.bottom
|
bottomSheet.peekHeight = playerHeight + navigationBarInsets.bottom
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setPlayerVisible(visible: Boolean) {
|
fun setPlayerVisible(visible_: Boolean?) {
|
||||||
|
val visible = if (visible_ != null) visible_ else if (bottomSheet.state == BottomSheetBehavior.STATE_COLLAPSED) false else true
|
||||||
|
|
||||||
bottomSheet.setLocked(!visible)
|
bottomSheet.setLocked(!visible)
|
||||||
if (visible) {
|
if (visible) {
|
||||||
bottomSheetCallback.onStateChanged(dummyView, bottomSheet.state) // Update toolbar visibility
|
bottomSheetCallback.onStateChanged(dummyView, bottomSheet.state) // Update toolbar visibility
|
||||||
|
@ -353,7 +355,7 @@ class MainActivity : CastEnabledActivity() {
|
||||||
params.setMargins(navigationBarInsets.left, 0, navigationBarInsets.right,
|
params.setMargins(navigationBarInsets.left, 0, navigationBarInsets.right,
|
||||||
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.playerFragment)
|
val playerView = findViewById<FragmentContainerView>(R.id.playerFragment1)
|
||||||
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
|
||||||
|
|
|
@ -15,6 +15,7 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.CompoundButton
|
import android.widget.CompoundButton
|
||||||
|
import androidx.annotation.OptIn
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
@ -28,6 +29,7 @@ import java.text.DecimalFormatSymbols
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
open class VariableSpeedDialog : BottomSheetDialogFragment() {
|
open class VariableSpeedDialog : BottomSheetDialogFragment() {
|
||||||
|
|
||||||
private lateinit var adapter: SpeedSelectionAdapter
|
private lateinit var adapter: SpeedSelectionAdapter
|
||||||
private lateinit var speedSeekBar: PlaybackSpeedSeekBar
|
private lateinit var speedSeekBar: PlaybackSpeedSeekBar
|
||||||
private lateinit var addCurrentSpeedChip: Chip
|
private lateinit var addCurrentSpeedChip: Chip
|
||||||
|
@ -38,6 +40,8 @@ open class VariableSpeedDialog : BottomSheetDialogFragment() {
|
||||||
private var controller: PlaybackController? = null
|
private var controller: PlaybackController? = null
|
||||||
private val selectedSpeeds: MutableList<Float>
|
private val selectedSpeeds: MutableList<Float>
|
||||||
|
|
||||||
|
private val settingCode: Array<Int> = Array(3) { 0 }
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val format = DecimalFormatSymbols(Locale.US)
|
val format = DecimalFormatSymbols(Locale.US)
|
||||||
format.decimalSeparator = '.'
|
format.decimalSeparator = '.'
|
||||||
|
@ -46,12 +50,14 @@ open class VariableSpeedDialog : BottomSheetDialogFragment() {
|
||||||
|
|
||||||
@UnstableApi override fun onStart() {
|
@UnstableApi override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
controller = object : PlaybackController(requireActivity()) {
|
if (controller == null) {
|
||||||
override fun loadMediaInfo() {
|
controller = object : PlaybackController(requireActivity()) {
|
||||||
if (controller != null) updateSpeed(SpeedChangedEvent(controller!!.currentPlaybackSpeedMultiplier))
|
override fun loadMediaInfo() {
|
||||||
|
if (controller != null) updateSpeed(SpeedChangedEvent(controller!!.currentPlaybackSpeedMultiplier))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
controller?.init()
|
||||||
}
|
}
|
||||||
controller?.init()
|
|
||||||
EventBus.getDefault().register(this)
|
EventBus.getDefault().register(this)
|
||||||
if (controller != null) updateSpeed(SpeedChangedEvent(controller!!.currentPlaybackSpeedMultiplier))
|
if (controller != null) updateSpeed(SpeedChangedEvent(controller!!.currentPlaybackSpeedMultiplier))
|
||||||
}
|
}
|
||||||
|
@ -73,6 +79,21 @@ open class VariableSpeedDialog : BottomSheetDialogFragment() {
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
_binding = SpeedSelectDialogBinding.inflate(inflater)
|
_binding = SpeedSelectDialogBinding.inflate(inflater)
|
||||||
|
|
||||||
|
val argument = arguments?.getString("default_setting")
|
||||||
|
|
||||||
|
when (argument) {
|
||||||
|
null, "Current" -> {
|
||||||
|
binding.currentAudio.isChecked = true
|
||||||
|
}
|
||||||
|
"Feed" -> {
|
||||||
|
binding.currentPodcast.isChecked = true
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
binding.global.isChecked = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
speedSeekBar = binding.speedSeekBar
|
speedSeekBar = binding.speedSeekBar
|
||||||
speedSeekBar.setProgressChangedListener { multiplier: Float ->
|
speedSeekBar.setProgressChangedListener { multiplier: Float ->
|
||||||
controller?.setPlaybackSpeed(multiplier)
|
controller?.setPlaybackSpeed(multiplier)
|
||||||
|
@ -95,15 +116,24 @@ open class VariableSpeedDialog : BottomSheetDialogFragment() {
|
||||||
skipSilence.isChecked = isSkipSilence
|
skipSilence.isChecked = isSkipSilence
|
||||||
skipSilence.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
|
skipSilence.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
|
||||||
isSkipSilence = isChecked
|
isSkipSilence = isChecked
|
||||||
controller!!.setSkipSilence(isChecked)
|
controller?.setSkipSilence(isChecked)
|
||||||
}
|
}
|
||||||
|
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class) override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
// if (controller == null || !controller!!.isPlaybackServiceReady()) {
|
||||||
|
// binding.currentPodcast.visibility = View.INVISIBLE
|
||||||
|
// } else binding.currentPodcast.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
_binding = null
|
_binding = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addCurrentSpeed() {
|
private fun addCurrentSpeed() {
|
||||||
val newSpeed = speedSeekBar.currentSpeed
|
val newSpeed = speedSeekBar.currentSpeed
|
||||||
if (selectedSpeeds.contains(newSpeed)) {
|
if (selectedSpeeds.contains(newSpeed)) {
|
||||||
|
@ -134,15 +164,16 @@ open class VariableSpeedDialog : BottomSheetDialogFragment() {
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
holder.chip.setOnClickListener {
|
holder.chip.setOnClickListener { Handler(Looper.getMainLooper()).postDelayed({
|
||||||
Handler(Looper.getMainLooper()).postDelayed(
|
if (binding.currentAudio.isChecked) settingCode[0] = 1
|
||||||
{
|
if (binding.currentPodcast.isChecked) settingCode[1] = 1
|
||||||
if (controller != null) {
|
if (binding.global.isChecked) settingCode[2] = 1
|
||||||
dismiss()
|
|
||||||
controller!!.setPlaybackSpeed(speed)
|
if (controller != null) {
|
||||||
}
|
dismiss()
|
||||||
}, 200)
|
controller!!.setPlaybackSpeed(speed, settingCode)
|
||||||
}
|
}
|
||||||
|
}, 200) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
|
@ -156,4 +187,16 @@ open class VariableSpeedDialog : BottomSheetDialogFragment() {
|
||||||
inner class ViewHolder internal constructor(var chip: Chip) : RecyclerView.ViewHolder(
|
inner class ViewHolder internal constructor(var chip: Chip) : RecyclerView.ViewHolder(
|
||||||
chip)
|
chip)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newInstance(argument: String? = null): VariableSpeedDialog {
|
||||||
|
val dialog = VariableSpeedDialog()
|
||||||
|
if (argument != null) {
|
||||||
|
val args = Bundle()
|
||||||
|
args.putString("default_setting", argument)
|
||||||
|
dialog.arguments = args
|
||||||
|
}
|
||||||
|
return dialog
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,23 @@ 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.databinding.AudioplayerFragmentBinding
|
||||||
|
import ac.mdiq.podcini.databinding.InternalPlayerFragmentBinding
|
||||||
|
import ac.mdiq.podcini.feed.util.ImageResourceUtils
|
||||||
import ac.mdiq.podcini.feed.util.PlaybackSpeedUtils
|
import ac.mdiq.podcini.feed.util.PlaybackSpeedUtils
|
||||||
import ac.mdiq.podcini.playback.PlaybackController
|
import ac.mdiq.podcini.playback.PlaybackController
|
||||||
import ac.mdiq.podcini.playback.base.PlayerStatus
|
import ac.mdiq.podcini.playback.base.PlayerStatus
|
||||||
import ac.mdiq.podcini.playback.cast.CastEnabledActivity
|
import ac.mdiq.podcini.playback.cast.CastEnabledActivity
|
||||||
import ac.mdiq.podcini.playback.event.*
|
import ac.mdiq.podcini.playback.event.PlaybackPositionEvent
|
||||||
|
import ac.mdiq.podcini.playback.event.PlaybackServiceEvent
|
||||||
|
import ac.mdiq.podcini.playback.event.SleepTimerUpdatedEvent
|
||||||
|
import ac.mdiq.podcini.playback.event.SpeedChangedEvent
|
||||||
import ac.mdiq.podcini.preferences.UserPreferences
|
import ac.mdiq.podcini.preferences.UserPreferences
|
||||||
import ac.mdiq.podcini.receiver.MediaButtonReceiver
|
import ac.mdiq.podcini.receiver.MediaButtonReceiver
|
||||||
|
import ac.mdiq.podcini.service.playback.PlaybackService
|
||||||
import ac.mdiq.podcini.storage.model.feed.Chapter
|
import ac.mdiq.podcini.storage.model.feed.Chapter
|
||||||
import ac.mdiq.podcini.storage.model.feed.FeedItem
|
import ac.mdiq.podcini.storage.model.feed.FeedItem
|
||||||
import ac.mdiq.podcini.storage.model.feed.FeedMedia
|
import ac.mdiq.podcini.storage.model.feed.FeedMedia
|
||||||
|
import ac.mdiq.podcini.storage.model.playback.MediaType
|
||||||
import ac.mdiq.podcini.storage.model.playback.Playable
|
import ac.mdiq.podcini.storage.model.playback.Playable
|
||||||
import ac.mdiq.podcini.ui.activity.MainActivity
|
import ac.mdiq.podcini.ui.activity.MainActivity
|
||||||
import ac.mdiq.podcini.ui.common.PlaybackSpeedIndicatorView
|
import ac.mdiq.podcini.ui.common.PlaybackSpeedIndicatorView
|
||||||
|
@ -27,7 +34,6 @@ import ac.mdiq.podcini.util.Converter
|
||||||
import ac.mdiq.podcini.util.TimeSpeedConverter
|
import ac.mdiq.podcini.util.TimeSpeedConverter
|
||||||
import ac.mdiq.podcini.util.event.FavoritesEvent
|
import ac.mdiq.podcini.util.event.FavoritesEvent
|
||||||
import ac.mdiq.podcini.util.event.PlayerErrorEvent
|
import ac.mdiq.podcini.util.event.PlayerErrorEvent
|
||||||
import ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
@ -37,15 +43,18 @@ import android.text.Html
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
import android.widget.ProgressBar
|
import android.widget.ImageView
|
||||||
import android.widget.SeekBar
|
import android.widget.SeekBar
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.OptIn
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
import androidx.core.app.ShareCompat
|
import androidx.core.app.ShareCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
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.elevation.SurfaceColors
|
import com.google.android.material.elevation.SurfaceColors
|
||||||
|
@ -70,35 +79,25 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
||||||
var _binding: AudioplayerFragmentBinding? = null
|
var _binding: AudioplayerFragmentBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
lateinit var butPlaybackSpeed: PlaybackSpeedIndicatorView
|
|
||||||
lateinit var txtvPlaybackSpeed: TextView
|
|
||||||
|
|
||||||
private lateinit var episodeTitle: TextView
|
|
||||||
private lateinit var itemDesrView: View
|
private lateinit var itemDesrView: View
|
||||||
private lateinit var txtvPosition: TextView
|
|
||||||
private lateinit var txtvLength: TextView
|
|
||||||
private lateinit var sbPosition: ChapterSeekBar
|
|
||||||
private lateinit var butRev: ImageButton
|
|
||||||
private lateinit var txtvRev: TextView
|
|
||||||
private lateinit var butPlay: PlayButton
|
|
||||||
private lateinit var butFF: ImageButton
|
|
||||||
private lateinit var txtvFF: TextView
|
|
||||||
private lateinit var butSkip: ImageButton
|
|
||||||
private lateinit var txtvSkip: TextView
|
|
||||||
private lateinit var toolbar: MaterialToolbar
|
|
||||||
private lateinit var playerFragment: View
|
|
||||||
|
|
||||||
private lateinit var progressIndicator: ProgressBar
|
private lateinit var toolbar: MaterialToolbar
|
||||||
|
private var playerFragment1: InternalPlayerFragment? = null
|
||||||
|
private var playerFragment2: InternalPlayerFragment? = null
|
||||||
|
private lateinit var playerView1: View
|
||||||
|
private lateinit var playerView2: View
|
||||||
|
|
||||||
private lateinit var cardViewSeek: CardView
|
private lateinit var cardViewSeek: CardView
|
||||||
private lateinit var txtvSeek: TextView
|
private lateinit var txtvSeek: TextView
|
||||||
|
|
||||||
private var controller: PlaybackController? = null
|
private var controller: PlaybackController? = null
|
||||||
private var disposable: Disposable? = null
|
private var disposable: Disposable? = null
|
||||||
private var showTimeLeft = false
|
|
||||||
private var seekedToChapterStart = false
|
private var seekedToChapterStart = false
|
||||||
private var currentChapterIndex = -1
|
private var currentChapterIndex = -1
|
||||||
private var duration = 0
|
private var duration = 0
|
||||||
|
|
||||||
|
private var currentMedia: Playable? = null
|
||||||
|
|
||||||
@SuppressLint("WrongConstant")
|
@SuppressLint("WrongConstant")
|
||||||
override fun onCreateView(inflater: LayoutInflater,
|
override fun onCreateView(inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
|
@ -120,50 +119,39 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
||||||
}
|
}
|
||||||
toolbar.setOnMenuItemClickListener(this)
|
toolbar.setOnMenuItemClickListener(this)
|
||||||
|
|
||||||
val externalPlayerFragment = ExternalPlayerFragment()
|
controller = newPlaybackController()
|
||||||
|
controller!!.init()
|
||||||
|
|
||||||
|
playerFragment1 = InternalPlayerFragment.newInstance(controller!!)
|
||||||
childFragmentManager.beginTransaction()
|
childFragmentManager.beginTransaction()
|
||||||
.replace(R.id.playerFragment, externalPlayerFragment, ExternalPlayerFragment.TAG)
|
.replace(R.id.playerFragment1, playerFragment1!!, InternalPlayerFragment.TAG)
|
||||||
.commit()
|
.commit()
|
||||||
// playerFragment = binding.playerFragment
|
playerView1 = binding.root.findViewById(R.id.playerFragment1)
|
||||||
playerFragment = binding.root.findViewById(R.id.playerFragment)
|
playerView1.setBackgroundColor(
|
||||||
playerFragment.setBackgroundColor(
|
SurfaceColors.getColorForElevation(requireContext(), 8 * resources.displayMetrics.density))
|
||||||
|
|
||||||
|
playerFragment2 = InternalPlayerFragment.newInstance(controller!!)
|
||||||
|
childFragmentManager.beginTransaction()
|
||||||
|
.replace(R.id.playerFragment2, playerFragment2!!, InternalPlayerFragment.TAG)
|
||||||
|
.commit()
|
||||||
|
playerView2 = binding.root.findViewById(R.id.playerFragment2)
|
||||||
|
playerView2.setBackgroundColor(
|
||||||
SurfaceColors.getColorForElevation(requireContext(), 8 * resources.displayMetrics.density))
|
SurfaceColors.getColorForElevation(requireContext(), 8 * resources.displayMetrics.density))
|
||||||
|
|
||||||
itemDesrView = binding.itemDescription
|
itemDesrView = binding.itemDescription
|
||||||
episodeTitle = binding.titleView
|
|
||||||
butPlaybackSpeed = binding.butPlaybackSpeed
|
|
||||||
txtvPlaybackSpeed = binding.txtvPlaybackSpeed
|
|
||||||
sbPosition = binding.sbPosition
|
|
||||||
txtvPosition = binding.txtvPosition
|
|
||||||
txtvLength = binding.txtvLength
|
|
||||||
butRev = binding.butRev
|
|
||||||
txtvRev = binding.txtvRev
|
|
||||||
butPlay = binding.butPlay
|
|
||||||
butFF = binding.butFF
|
|
||||||
txtvFF = binding.txtvFF
|
|
||||||
butSkip = binding.butSkip
|
|
||||||
txtvSkip = binding.txtvSkip
|
|
||||||
progressIndicator = binding.progLoading
|
|
||||||
cardViewSeek = binding.cardViewSeek
|
cardViewSeek = binding.cardViewSeek
|
||||||
txtvSeek = binding.txtvSeek
|
txtvSeek = binding.txtvSeek
|
||||||
|
|
||||||
setupLengthTextView()
|
|
||||||
setupControlButtons()
|
|
||||||
butPlaybackSpeed.setOnClickListener {
|
|
||||||
VariableSpeedDialog().show(childFragmentManager, null)
|
|
||||||
}
|
|
||||||
sbPosition.setOnSeekBarChangeListener(this)
|
|
||||||
|
|
||||||
val fm = requireActivity().supportFragmentManager
|
val fm = requireActivity().supportFragmentManager
|
||||||
val transaction = fm.beginTransaction()
|
val transaction = fm.beginTransaction()
|
||||||
val itemDescFrag = PlayerDetailsFragment()
|
val itemDescFrag = PlayerDetailsFragment()
|
||||||
transaction.replace(R.id.itemDescription, itemDescFrag).commit()
|
transaction.replace(R.id.itemDescription, itemDescFrag).commit()
|
||||||
|
|
||||||
controller = newPlaybackController()
|
// controller = externalPlayerFragment1.controller
|
||||||
controller?.init()
|
// loadMediaInfo(false)
|
||||||
loadMediaInfo(false)
|
|
||||||
EventBus.getDefault().register(this)
|
EventBus.getDefault().register(this)
|
||||||
|
|
||||||
|
// updateUi(controller?.getMedia())
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,62 +177,13 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
||||||
dividerPos[i] = chapters[i].start / duration.toFloat()
|
dividerPos[i] = chapters[i].start / duration.toFloat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sbPosition.setDividerPos(dividerPos)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupControlButtons() {
|
// @Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
butRev.setOnClickListener {
|
// fun onUnreadItemsUpdate(event: UnreadItemsUpdateEvent?) {
|
||||||
if (controller != null) {
|
// if (controller == null) return
|
||||||
val curr: Int = controller!!.position
|
// updatePosition(PlaybackPositionEvent(controller!!.position, controller!!.duration))
|
||||||
controller!!.seekTo(curr - UserPreferences.rewindSecs * 1000)
|
// }
|
||||||
}
|
|
||||||
}
|
|
||||||
butRev.setOnLongClickListener {
|
|
||||||
SkipPreferenceDialog.showSkipPreference(requireContext(),
|
|
||||||
SkipPreferenceDialog.SkipDirection.SKIP_REWIND, txtvRev)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
butPlay.setOnClickListener {
|
|
||||||
controller?.init()
|
|
||||||
controller?.playPause()
|
|
||||||
}
|
|
||||||
butPlay.setOnLongClickListener {
|
|
||||||
if (controller != null && controller!!.status == PlayerStatus.PLAYING) {
|
|
||||||
val fallbackSpeed = UserPreferences.fallbackSpeed
|
|
||||||
if (fallbackSpeed > 0.1f) controller!!.fallbackSpeed(fallbackSpeed)
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
butFF.setOnClickListener {
|
|
||||||
if (controller != null) {
|
|
||||||
val curr: Int = controller!!.position
|
|
||||||
controller!!.seekTo(curr + UserPreferences.fastForwardSecs * 1000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
butFF.setOnLongClickListener {
|
|
||||||
SkipPreferenceDialog.showSkipPreference(requireContext(),
|
|
||||||
SkipPreferenceDialog.SkipDirection.SKIP_FORWARD, txtvFF)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
butSkip.setOnClickListener {
|
|
||||||
if (controller != null && controller!!.status == PlayerStatus.PLAYING) {
|
|
||||||
val speedForward = UserPreferences.speedforwardSpeed
|
|
||||||
if (speedForward > 0.1f) controller!!.speedForward(speedForward)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
butSkip.setOnLongClickListener {
|
|
||||||
activity?.sendBroadcast(
|
|
||||||
MediaButtonReceiver.createIntent(requireContext(), KeyEvent.KEYCODE_MEDIA_NEXT))
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
|
||||||
fun onUnreadItemsUpdate(event: UnreadItemsUpdateEvent?) {
|
|
||||||
if (controller == null) return
|
|
||||||
updatePosition(PlaybackPositionEvent(controller!!.position, controller!!.duration))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
fun onPlaybackServiceChanged(event: PlaybackServiceEvent) {
|
fun onPlaybackServiceChanged(event: PlaybackServiceEvent) {
|
||||||
|
@ -253,53 +192,62 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupLengthTextView() {
|
// private fun setupLengthTextView() {
|
||||||
showTimeLeft = UserPreferences.shouldShowRemainingTime()
|
// showTimeLeft = UserPreferences.shouldShowRemainingTime()
|
||||||
txtvLength.setOnClickListener(View.OnClickListener {
|
// txtvLength.setOnClickListener(View.OnClickListener {
|
||||||
if (controller == null) return@OnClickListener
|
// if (controller == null) return@OnClickListener
|
||||||
|
//
|
||||||
|
// showTimeLeft = !showTimeLeft
|
||||||
|
// UserPreferences.setShowRemainTimeSetting(showTimeLeft)
|
||||||
|
// updatePosition(PlaybackPositionEvent(controller!!.position, controller!!.duration))
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
showTimeLeft = !showTimeLeft
|
// @Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
UserPreferences.setShowRemainTimeSetting(showTimeLeft)
|
// fun updatePlaybackSpeedButton(event: SpeedChangedEvent) {
|
||||||
updatePosition(PlaybackPositionEvent(controller!!.position, controller!!.duration))
|
// val speedStr: String = DecimalFormat("0.00").format(event.newSpeed.toDouble())
|
||||||
})
|
// txtvPlaybackSpeed.text = speedStr
|
||||||
}
|
// butPlaybackSpeed.setSpeed(event.newSpeed)
|
||||||
|
// }
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
|
||||||
fun updatePlaybackSpeedButton(event: SpeedChangedEvent) {
|
|
||||||
val speedStr: String = DecimalFormat("0.00").format(event.newSpeed.toDouble())
|
|
||||||
txtvPlaybackSpeed.text = speedStr
|
|
||||||
butPlaybackSpeed.setSpeed(event.newSpeed)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadMediaInfo(includingChapters: Boolean) {
|
private fun loadMediaInfo(includingChapters: Boolean) {
|
||||||
Log.d(TAG, "loadMediaInfo called")
|
Log.d(TAG, "loadMediaInfo called")
|
||||||
disposable?.dispose()
|
|
||||||
disposable = Maybe.create<Playable> { emitter: MaybeEmitter<Playable?> ->
|
val theMedia = controller?.getMedia() ?: return
|
||||||
val media: Playable? = controller?.getMedia()
|
if (currentMedia == null || theMedia?.getIdentifier() != currentMedia?.getIdentifier()) {
|
||||||
if (media != null) {
|
Log.d(TAG, "loadMediaInfo loading details")
|
||||||
if (includingChapters) {
|
disposable?.dispose()
|
||||||
ChapterUtils.loadChapters(media, requireContext(), false)
|
disposable = Maybe.create<Playable> { emitter: MaybeEmitter<Playable?> ->
|
||||||
|
val media: Playable? = theMedia
|
||||||
|
if (media != null) {
|
||||||
|
if (includingChapters) {
|
||||||
|
ChapterUtils.loadChapters(media, requireContext(), false)
|
||||||
|
}
|
||||||
|
emitter.onSuccess(media)
|
||||||
|
} else {
|
||||||
|
emitter.onComplete()
|
||||||
}
|
}
|
||||||
emitter.onSuccess(media)
|
|
||||||
} else {
|
|
||||||
emitter.onComplete()
|
|
||||||
}
|
}
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe({ media: Playable ->
|
||||||
|
currentMedia = media
|
||||||
|
updateUi(media)
|
||||||
|
playerFragment1?.updateUi(media)
|
||||||
|
playerFragment2?.updateUi(media)
|
||||||
|
if (!includingChapters) {
|
||||||
|
loadMediaInfo(true)
|
||||||
|
}
|
||||||
|
}, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) },
|
||||||
|
{ updateUi(null) })
|
||||||
}
|
}
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe({ media: Playable ->
|
|
||||||
updateUi(media)
|
|
||||||
if (!includingChapters) {
|
|
||||||
loadMediaInfo(true)
|
|
||||||
}
|
|
||||||
}, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) },
|
|
||||||
{ updateUi(null) })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun newPlaybackController(): PlaybackController {
|
private fun newPlaybackController(): PlaybackController {
|
||||||
return object : PlaybackController(requireActivity()) {
|
return object : PlaybackController(requireActivity()) {
|
||||||
override fun updatePlayButtonShowsPlay(showPlay: Boolean) {
|
override fun updatePlayButtonShowsPlay(showPlay: Boolean) {
|
||||||
butPlay.setIsShowPlay(showPlay)
|
playerFragment1?.butPlay?.setIsShowPlay(showPlay)
|
||||||
|
playerFragment2?.butPlay?.setIsShowPlay(showPlay)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadMediaInfo() {
|
override fun loadMediaInfo() {
|
||||||
|
@ -307,19 +255,15 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPlaybackEnd() {
|
override fun onPlaybackEnd() {
|
||||||
(activity as MainActivity).bottomSheet.state = BottomSheetBehavior.STATE_EXPANDED
|
playerFragment1?.butPlay?.setIsShowPlay(true)
|
||||||
|
playerFragment2?.butPlay?.setIsShowPlay(true)
|
||||||
|
(activity as MainActivity).setPlayerVisible(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateUi(media: Playable?) {
|
private fun updateUi(media: Playable?) {
|
||||||
if (controller != null) duration = controller!!.duration
|
|
||||||
if (media == null) return
|
|
||||||
Log.d(TAG, "updateUi called")
|
Log.d(TAG, "updateUi called")
|
||||||
|
|
||||||
episodeTitle.text = media.getEpisodeTitle()
|
|
||||||
updatePosition(PlaybackPositionEvent(media.getPosition(), media.getDuration()))
|
|
||||||
updatePlaybackSpeedButton(SpeedChangedEvent(PlaybackSpeedUtils.getCurrentPlaybackSpeed(media)))
|
|
||||||
setChapterDividers(media)
|
setChapterDividers(media)
|
||||||
setupOptionsMenu(media)
|
setupOptionsMenu(media)
|
||||||
}
|
}
|
||||||
|
@ -339,73 +283,33 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
txtvRev.text = NumberFormat.getInstance().format(UserPreferences.rewindSecs.toLong())
|
loadMediaInfo(false)
|
||||||
txtvRev.text = NumberFormat.getInstance().format(UserPreferences.rewindSecs.toLong())
|
|
||||||
txtvFF.text = NumberFormat.getInstance().format(UserPreferences.fastForwardSecs.toLong())
|
|
||||||
if (UserPreferences.speedforwardSpeed > 0.1f) {
|
|
||||||
txtvSkip.text = NumberFormat.getInstance().format(UserPreferences.speedforwardSpeed)
|
|
||||||
} else txtvSkip.visibility = View.GONE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
super.onStop()
|
super.onStop()
|
||||||
progressIndicator.visibility = View.GONE // Controller released; we will not receive buffering updates
|
// progressIndicator.visibility = View.GONE // Controller released; we will not receive buffering updates
|
||||||
disposable?.dispose()
|
disposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
// @Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
@Suppress("unused")
|
// @Suppress("unused")
|
||||||
fun bufferUpdate(event: BufferUpdateEvent) {
|
// fun bufferUpdate(event: BufferUpdateEvent) {
|
||||||
when {
|
// when {
|
||||||
event.hasStarted() -> {
|
// event.hasStarted() -> {
|
||||||
progressIndicator.visibility = View.VISIBLE
|
// progressIndicator.visibility = View.VISIBLE
|
||||||
}
|
// }
|
||||||
event.hasEnded() -> {
|
// event.hasEnded() -> {
|
||||||
progressIndicator.visibility = View.GONE
|
// progressIndicator.visibility = View.GONE
|
||||||
}
|
// }
|
||||||
controller != null && controller!!.isStreaming -> {
|
//// controller != null && controller!!.isStreaming -> {
|
||||||
sbPosition.setSecondaryProgress((event.progress * sbPosition.max).toInt())
|
//// sbPosition.setSecondaryProgress((event.progress * sbPosition.max).toInt())
|
||||||
}
|
//// }
|
||||||
else -> {
|
// else -> {
|
||||||
sbPosition.setSecondaryProgress(0)
|
//// sbPosition.setSecondaryProgress(0)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
@UnstableApi
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
|
||||||
fun updatePosition(event: PlaybackPositionEvent) {
|
|
||||||
if (controller == null) return
|
|
||||||
|
|
||||||
val converter = TimeSpeedConverter(controller!!.currentPlaybackSpeedMultiplier)
|
|
||||||
val currentPosition: Int = converter.convert(event.position)
|
|
||||||
val duration: Int = converter.convert(event.duration)
|
|
||||||
val remainingTime: Int = converter.convert(max((event.duration - event.position).toDouble(), 0.0).toInt())
|
|
||||||
currentChapterIndex = ChapterUtils.getCurrentChapterIndex(controller!!.getMedia(), currentPosition)
|
|
||||||
// Log.d(TAG, "currentPosition " + Converter.getDurationStringLong(currentPosition));
|
|
||||||
if (currentPosition == Playable.INVALID_TIME || duration == Playable.INVALID_TIME) {
|
|
||||||
Log.w(TAG, "Could not react to position observer update because of invalid time $currentPosition $duration")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
txtvPosition.text = Converter.getDurationStringLong(currentPosition)
|
|
||||||
txtvPosition.setContentDescription(getString(R.string.position,
|
|
||||||
Converter.getDurationStringLocalized(requireContext(), currentPosition.toLong())))
|
|
||||||
showTimeLeft = UserPreferences.shouldShowRemainingTime()
|
|
||||||
if (showTimeLeft) {
|
|
||||||
txtvLength.setContentDescription(getString(R.string.remaining_time,
|
|
||||||
Converter.getDurationStringLocalized(requireContext(), remainingTime.toLong())))
|
|
||||||
txtvLength.text = (if (remainingTime > 0) "-" else "") + Converter.getDurationStringLong(remainingTime)
|
|
||||||
} else {
|
|
||||||
txtvLength.setContentDescription(getString(R.string.chapter_duration,
|
|
||||||
Converter.getDurationStringLocalized(requireContext(), duration.toLong())))
|
|
||||||
txtvLength.text = Converter.getDurationStringLong(duration)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sbPosition.isPressed && event.duration > 0) {
|
|
||||||
val progress: Float = (event.position.toFloat()) / event.duration
|
|
||||||
sbPosition.progress = (progress * sbPosition.max).toInt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
fun favoritesChanged(event: FavoritesEvent?) {
|
fun favoritesChanged(event: FavoritesEvent?) {
|
||||||
|
@ -427,15 +331,15 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
||||||
var position: Int = converter.convert((prog * controller!!.duration).toInt())
|
var position: Int = converter.convert((prog * controller!!.duration).toInt())
|
||||||
val newChapterIndex: Int = ChapterUtils.getCurrentChapterIndex(controller!!.getMedia(), position)
|
val newChapterIndex: Int = ChapterUtils.getCurrentChapterIndex(controller!!.getMedia(), position)
|
||||||
if (newChapterIndex > -1) {
|
if (newChapterIndex > -1) {
|
||||||
if (!sbPosition.isPressed && currentChapterIndex != newChapterIndex) {
|
// if (!sbPosition.isPressed && currentChapterIndex != newChapterIndex) {
|
||||||
currentChapterIndex = newChapterIndex
|
// currentChapterIndex = newChapterIndex
|
||||||
val media = controller!!.getMedia()
|
// val media = controller!!.getMedia()
|
||||||
position = media?.getChapters()?.get(currentChapterIndex)?.start?.toInt() ?: 0
|
// position = media?.getChapters()?.get(currentChapterIndex)?.start?.toInt() ?: 0
|
||||||
seekedToChapterStart = true
|
// seekedToChapterStart = true
|
||||||
controller!!.seekTo(position)
|
// controller!!.seekTo(position)
|
||||||
updateUi(controller!!.getMedia())
|
// updateUi(controller!!.getMedia())
|
||||||
sbPosition.highlightCurrentChapter()
|
// sbPosition.highlightCurrentChapter()
|
||||||
}
|
// }
|
||||||
txtvSeek.text = controller!!.getMedia()?.getChapters()?.get(newChapterIndex)?.title ?: (""
|
txtvSeek.text = controller!!.getMedia()?.getChapters()?.get(newChapterIndex)?.title ?: (""
|
||||||
+ "\n" + Converter.getDurationStringLong(position))
|
+ "\n" + Converter.getDurationStringLong(position))
|
||||||
} else {
|
} else {
|
||||||
|
@ -538,7 +442,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
||||||
|
|
||||||
fun fadePlayerToToolbar(slideOffset: Float) {
|
fun fadePlayerToToolbar(slideOffset: Float) {
|
||||||
val playerFadeProgress = (max(0.0, min(0.2, (slideOffset - 0.2f).toDouble())) / 0.2f).toFloat()
|
val playerFadeProgress = (max(0.0, min(0.2, (slideOffset - 0.2f).toDouble())) / 0.2f).toFloat()
|
||||||
val player = playerFragment
|
val player = playerView1
|
||||||
player.alpha = 1 - playerFadeProgress
|
player.alpha = 1 - playerFadeProgress
|
||||||
player.visibility = if (playerFadeProgress > 0.99f) View.INVISIBLE else View.VISIBLE
|
player.visibility = if (playerFadeProgress > 0.99f) View.INVISIBLE else View.VISIBLE
|
||||||
val toolbarFadeProgress = (max(0.0, min(0.2, (slideOffset - 0.6f).toDouble())) / 0.2f).toFloat()
|
val toolbarFadeProgress = (max(0.0, min(0.2, (slideOffset - 0.6f).toDouble())) / 0.2f).toFloat()
|
||||||
|
@ -546,6 +450,297 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
||||||
toolbar.visibility = if (toolbarFadeProgress < 0.01f) View.INVISIBLE else View.VISIBLE
|
toolbar.visibility = if (toolbarFadeProgress < 0.01f) View.INVISIBLE else View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class InternalPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
||||||
|
private var _binding: InternalPlayerFragmentBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
private lateinit var imgvCover: ImageView
|
||||||
|
lateinit var butPlay: PlayButton
|
||||||
|
|
||||||
|
lateinit var butPlaybackSpeed: PlaybackSpeedIndicatorView
|
||||||
|
lateinit var txtvPlaybackSpeed: TextView
|
||||||
|
|
||||||
|
private lateinit var episodeTitle: TextView
|
||||||
|
private lateinit var butRev: ImageButton
|
||||||
|
private lateinit var txtvRev: TextView
|
||||||
|
private lateinit var butFF: ImageButton
|
||||||
|
private lateinit var txtvFF: TextView
|
||||||
|
private lateinit var butSkip: ImageButton
|
||||||
|
private lateinit var txtvSkip: TextView
|
||||||
|
|
||||||
|
private lateinit var txtvPosition: TextView
|
||||||
|
private lateinit var txtvLength: TextView
|
||||||
|
private lateinit var sbPosition: ChapterSeekBar
|
||||||
|
|
||||||
|
private var showTimeLeft = false
|
||||||
|
|
||||||
|
private var disposable: Disposable? = null
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?): View {
|
||||||
|
_binding = InternalPlayerFragmentBinding.inflate(inflater)
|
||||||
|
Log.d(TAG, "fragment onCreateView")
|
||||||
|
|
||||||
|
episodeTitle = binding.titleView
|
||||||
|
butPlaybackSpeed = binding.butPlaybackSpeed
|
||||||
|
txtvPlaybackSpeed = binding.txtvPlaybackSpeed
|
||||||
|
imgvCover = binding.imgvCover
|
||||||
|
butPlay = binding.butPlay
|
||||||
|
butRev = binding.butRev
|
||||||
|
txtvRev = binding.txtvRev
|
||||||
|
butFF = binding.butFF
|
||||||
|
txtvFF = binding.txtvFF
|
||||||
|
butSkip = binding.butSkip
|
||||||
|
txtvSkip = binding.txtvSkip
|
||||||
|
sbPosition = binding.sbPosition
|
||||||
|
txtvPosition = binding.txtvPosition
|
||||||
|
txtvLength = binding.txtvLength
|
||||||
|
|
||||||
|
setupLengthTextView()
|
||||||
|
setupControlButtons()
|
||||||
|
butPlaybackSpeed.setOnClickListener {
|
||||||
|
VariableSpeedDialog.newInstance(null).show(childFragmentManager, null)
|
||||||
|
}
|
||||||
|
sbPosition.setOnSeekBarChangeListener(this)
|
||||||
|
|
||||||
|
binding.internalPlayerFragment.setOnClickListener {
|
||||||
|
Log.d(TAG, "internalPlayerFragment was clicked")
|
||||||
|
val media = controller?.getMedia()
|
||||||
|
if (media != null) {
|
||||||
|
if (media.getMediaType() == MediaType.AUDIO) {
|
||||||
|
(activity as MainActivity).bottomSheet.setState(BottomSheetBehavior.STATE_EXPANDED)
|
||||||
|
} else {
|
||||||
|
val intent = PlaybackService.getPlayerActivityIntent(requireContext(), media)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EventBus.getDefault().register(this)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class) override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
EventBus.getDefault().unregister(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
butPlay.setOnClickListener {
|
||||||
|
if (controller == null) return@setOnClickListener
|
||||||
|
|
||||||
|
val media = controller!!.getMedia()
|
||||||
|
if (media?.getMediaType() == MediaType.VIDEO && controller!!.status != PlayerStatus.PLAYING) {
|
||||||
|
controller!!.playPause()
|
||||||
|
requireContext().startActivity(PlaybackService.getPlayerActivityIntent(requireContext(), media))
|
||||||
|
} else {
|
||||||
|
controller!!.playPause()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class) private fun setupControlButtons() {
|
||||||
|
butRev.setOnClickListener {
|
||||||
|
if (controller != null) {
|
||||||
|
val curr: Int = controller!!.position
|
||||||
|
controller!!.seekTo(curr - UserPreferences.rewindSecs * 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
butRev.setOnLongClickListener {
|
||||||
|
SkipPreferenceDialog.showSkipPreference(requireContext(),
|
||||||
|
SkipPreferenceDialog.SkipDirection.SKIP_REWIND, txtvRev)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
butPlay.setOnClickListener {
|
||||||
|
controller?.init()
|
||||||
|
controller?.playPause()
|
||||||
|
}
|
||||||
|
butPlay.setOnLongClickListener {
|
||||||
|
if (controller != null && controller!!.status == PlayerStatus.PLAYING) {
|
||||||
|
val fallbackSpeed = UserPreferences.fallbackSpeed
|
||||||
|
if (fallbackSpeed > 0.1f) controller!!.fallbackSpeed(fallbackSpeed)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
butFF.setOnClickListener {
|
||||||
|
if (controller != null) {
|
||||||
|
val curr: Int = controller!!.position
|
||||||
|
controller!!.seekTo(curr + UserPreferences.fastForwardSecs * 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
butFF.setOnLongClickListener {
|
||||||
|
SkipPreferenceDialog.showSkipPreference(requireContext(),
|
||||||
|
SkipPreferenceDialog.SkipDirection.SKIP_FORWARD, txtvFF)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
butSkip.setOnClickListener {
|
||||||
|
if (controller != null && controller!!.status == PlayerStatus.PLAYING) {
|
||||||
|
val speedForward = UserPreferences.speedforwardSpeed
|
||||||
|
if (speedForward > 0.1f) controller!!.speedForward(speedForward)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
butSkip.setOnLongClickListener {
|
||||||
|
activity?.sendBroadcast(
|
||||||
|
MediaButtonReceiver.createIntent(requireContext(), KeyEvent.KEYCODE_MEDIA_NEXT))
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class) private fun setupLengthTextView() {
|
||||||
|
showTimeLeft = UserPreferences.shouldShowRemainingTime()
|
||||||
|
txtvLength.setOnClickListener(View.OnClickListener {
|
||||||
|
if (controller == null) {
|
||||||
|
return@OnClickListener
|
||||||
|
}
|
||||||
|
showTimeLeft = !showTimeLeft
|
||||||
|
UserPreferences.setShowRemainTimeSetting(showTimeLeft)
|
||||||
|
onPositionObserverUpdate(PlaybackPositionEvent(controller!!.position, controller!!.duration))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
|
fun updatePlaybackSpeedButton(event: SpeedChangedEvent) {
|
||||||
|
val speedStr: String = DecimalFormat("0.00").format(event.newSpeed.toDouble())
|
||||||
|
txtvPlaybackSpeed.text = speedStr
|
||||||
|
butPlaybackSpeed.setSpeed(event.newSpeed)
|
||||||
|
}
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
|
fun onPositionObserverUpdate(event: PlaybackPositionEvent) {
|
||||||
|
if (controller == null || controller!!.position == Playable.INVALID_TIME || controller!!.duration == Playable.INVALID_TIME) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val converter = TimeSpeedConverter(controller!!.currentPlaybackSpeedMultiplier)
|
||||||
|
val currentPosition: Int = converter.convert(event.position)
|
||||||
|
val duration: Int = converter.convert(event.duration)
|
||||||
|
val remainingTime: Int = converter.convert(max((event.duration - event.position).toDouble(), 0.0).toInt())
|
||||||
|
if (currentPosition == Playable.INVALID_TIME || duration == Playable.INVALID_TIME) {
|
||||||
|
Log.w(TAG, "Could not react to position observer update because of invalid time")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
txtvPosition.text = Converter.getDurationStringLong(currentPosition)
|
||||||
|
txtvPosition.setContentDescription(getString(R.string.position,
|
||||||
|
Converter.getDurationStringLocalized(requireContext(), currentPosition.toLong())))
|
||||||
|
val showTimeLeft = UserPreferences.shouldShowRemainingTime()
|
||||||
|
if (showTimeLeft) {
|
||||||
|
txtvLength.setContentDescription(getString(R.string.remaining_time,
|
||||||
|
Converter.getDurationStringLocalized(requireContext(), remainingTime.toLong())))
|
||||||
|
txtvLength.text = (if (remainingTime > 0) "-" else "") + Converter.getDurationStringLong(remainingTime)
|
||||||
|
} else {
|
||||||
|
txtvLength.setContentDescription(getString(R.string.chapter_duration,
|
||||||
|
Converter.getDurationStringLocalized(requireContext(), duration.toLong())))
|
||||||
|
txtvLength.text = Converter.getDurationStringLong(duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sbPosition.isPressed) {
|
||||||
|
val progress: Float = (event.position.toFloat()) / event.duration
|
||||||
|
sbPosition.progress = (progress * sbPosition.max).toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
|
fun onPlaybackServiceChanged(event: PlaybackServiceEvent) {
|
||||||
|
when (event.action) {
|
||||||
|
PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN -> {
|
||||||
|
(activity as MainActivity).setPlayerVisible(false)
|
||||||
|
}
|
||||||
|
PlaybackServiceEvent.Action.SERVICE_STARTED -> {
|
||||||
|
(activity as MainActivity).setPlayerVisible(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
Log.d(TAG, "Fragment is about to be destroyed")
|
||||||
|
disposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class) override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
txtvRev.text = NumberFormat.getInstance().format(UserPreferences.rewindSecs.toLong())
|
||||||
|
txtvFF.text = NumberFormat.getInstance().format(UserPreferences.fastForwardSecs.toLong())
|
||||||
|
if (UserPreferences.speedforwardSpeed > 0.1f) {
|
||||||
|
txtvSkip.text = NumberFormat.getInstance().format(UserPreferences.speedforwardSpeed)
|
||||||
|
} else txtvSkip.visibility = View.GONE
|
||||||
|
val media = controller?.getMedia() ?: return
|
||||||
|
updatePlaybackSpeedButton(SpeedChangedEvent(PlaybackSpeedUtils.getCurrentPlaybackSpeed(media)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
controller?.pause()
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class) override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {}
|
||||||
|
|
||||||
|
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class) override fun onStopTrackingTouch(seekBar: SeekBar) {
|
||||||
|
if (controller != null) {
|
||||||
|
val prog: Float = seekBar.progress / (seekBar.max.toFloat())
|
||||||
|
controller!!.seekTo((prog * controller!!.duration).toInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
fun updateUi(media: Playable?) {
|
||||||
|
if (media == null) return
|
||||||
|
Log.d(TAG, "updateUi called")
|
||||||
|
|
||||||
|
episodeTitle.text = media.getEpisodeTitle()
|
||||||
|
(activity as MainActivity).setPlayerVisible(true)
|
||||||
|
onPositionObserverUpdate(PlaybackPositionEvent(media.getPosition(), media.getDuration()))
|
||||||
|
|
||||||
|
val options = RequestOptions()
|
||||||
|
.placeholder(R.color.light_gray)
|
||||||
|
.error(R.color.light_gray)
|
||||||
|
.fitCenter()
|
||||||
|
.dontAnimate()
|
||||||
|
|
||||||
|
val imgLoc = ImageResourceUtils.getEpisodeListImageLocation(media)
|
||||||
|
val imgLocFB = ImageResourceUtils.getFallbackImageLocation(media)
|
||||||
|
when {
|
||||||
|
!imgLoc.isNullOrBlank() -> Glide.with(this)
|
||||||
|
.load(imgLoc)
|
||||||
|
.apply(options)
|
||||||
|
.into(imgvCover)
|
||||||
|
!imgLocFB.isNullOrBlank() -> Glide.with(this)
|
||||||
|
.load(imgLocFB)
|
||||||
|
.apply(options)
|
||||||
|
.into(imgvCover)
|
||||||
|
else -> imgvCover.setImageResource(R.mipmap.ic_launcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controller?.isPlayingVideoLocally == true) {
|
||||||
|
(activity as MainActivity).bottomSheet.setLocked(true)
|
||||||
|
(activity as MainActivity).bottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED)
|
||||||
|
} else {
|
||||||
|
butPlay.visibility = View.VISIBLE
|
||||||
|
(activity as MainActivity).bottomSheet.setLocked(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG: String = "InternalPlayerFragment"
|
||||||
|
|
||||||
|
var controller: PlaybackController? = null
|
||||||
|
|
||||||
|
fun newInstance(controller_: PlaybackController) : InternalPlayerFragment {
|
||||||
|
controller = controller_
|
||||||
|
return InternalPlayerFragment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG: String = "AudioPlayerFragment"
|
const val TAG: String = "AudioPlayerFragment"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,387 +0,0 @@
|
||||||
package ac.mdiq.podcini.ui.fragment
|
|
||||||
|
|
||||||
import ac.mdiq.podcini.R
|
|
||||||
import ac.mdiq.podcini.databinding.ExternalPlayerFragmentBinding
|
|
||||||
import ac.mdiq.podcini.feed.util.ImageResourceUtils.getEpisodeListImageLocation
|
|
||||||
import ac.mdiq.podcini.feed.util.ImageResourceUtils.getFallbackImageLocation
|
|
||||||
import ac.mdiq.podcini.feed.util.PlaybackSpeedUtils
|
|
||||||
import ac.mdiq.podcini.playback.PlaybackController
|
|
||||||
import ac.mdiq.podcini.playback.base.PlayerStatus
|
|
||||||
import ac.mdiq.podcini.playback.event.PlaybackPositionEvent
|
|
||||||
import ac.mdiq.podcini.playback.event.PlaybackServiceEvent
|
|
||||||
import ac.mdiq.podcini.playback.event.SpeedChangedEvent
|
|
||||||
import ac.mdiq.podcini.preferences.UserPreferences
|
|
||||||
import ac.mdiq.podcini.receiver.MediaButtonReceiver
|
|
||||||
import ac.mdiq.podcini.service.playback.PlaybackService.Companion.getPlayerActivityIntent
|
|
||||||
import ac.mdiq.podcini.storage.model.playback.MediaType
|
|
||||||
import ac.mdiq.podcini.storage.model.playback.Playable
|
|
||||||
import ac.mdiq.podcini.ui.activity.MainActivity
|
|
||||||
import ac.mdiq.podcini.ui.common.PlaybackSpeedIndicatorView
|
|
||||||
import ac.mdiq.podcini.ui.dialog.SkipPreferenceDialog
|
|
||||||
import ac.mdiq.podcini.ui.dialog.VariableSpeedDialog
|
|
||||||
import ac.mdiq.podcini.ui.view.ChapterSeekBar
|
|
||||||
import ac.mdiq.podcini.ui.view.PlayButton
|
|
||||||
import ac.mdiq.podcini.util.Converter
|
|
||||||
import ac.mdiq.podcini.util.TimeSpeedConverter
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.KeyEvent
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.ImageButton
|
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.SeekBar
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.annotation.OptIn
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.media3.common.util.UnstableApi
|
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.bumptech.glide.request.RequestOptions
|
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
||||||
import io.reactivex.Maybe
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
|
||||||
import io.reactivex.disposables.Disposable
|
|
||||||
import io.reactivex.schedulers.Schedulers
|
|
||||||
import org.greenrobot.eventbus.EventBus
|
|
||||||
import org.greenrobot.eventbus.Subscribe
|
|
||||||
import org.greenrobot.eventbus.ThreadMode
|
|
||||||
import java.text.DecimalFormat
|
|
||||||
import java.text.NumberFormat
|
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fragment which is supposed to be displayed outside of the MediaplayerActivity.
|
|
||||||
*/
|
|
||||||
class ExternalPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
|
||||||
private var _binding: ExternalPlayerFragmentBinding? = null
|
|
||||||
private val binding get() = _binding!!
|
|
||||||
|
|
||||||
private lateinit var imgvCover: ImageView
|
|
||||||
private lateinit var butPlay: PlayButton
|
|
||||||
|
|
||||||
lateinit var butPlaybackSpeed: PlaybackSpeedIndicatorView
|
|
||||||
lateinit var txtvPlaybackSpeed: TextView
|
|
||||||
|
|
||||||
private lateinit var episodeTitle: TextView
|
|
||||||
private lateinit var butRev: ImageButton
|
|
||||||
private lateinit var txtvRev: TextView
|
|
||||||
private lateinit var butFF: ImageButton
|
|
||||||
private lateinit var txtvFF: TextView
|
|
||||||
private lateinit var butSkip: ImageButton
|
|
||||||
private lateinit var txtvSkip: TextView
|
|
||||||
|
|
||||||
private lateinit var txtvPosition: TextView
|
|
||||||
private lateinit var txtvLength: TextView
|
|
||||||
private lateinit var sbPosition: ChapterSeekBar
|
|
||||||
|
|
||||||
private var showTimeLeft = false
|
|
||||||
|
|
||||||
private var currentMedia: Playable? = null
|
|
||||||
|
|
||||||
private var controller: PlaybackController? = null
|
|
||||||
private var disposable: Disposable? = null
|
|
||||||
|
|
||||||
@UnstableApi
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?): View {
|
|
||||||
_binding = ExternalPlayerFragmentBinding.inflate(inflater)
|
|
||||||
Log.d(TAG, "fragment onCreateView")
|
|
||||||
|
|
||||||
episodeTitle = binding.titleView
|
|
||||||
butPlaybackSpeed = binding.butPlaybackSpeed
|
|
||||||
txtvPlaybackSpeed = binding.txtvPlaybackSpeed
|
|
||||||
imgvCover = binding.imgvCover
|
|
||||||
butPlay = binding.butPlay
|
|
||||||
butRev = binding.butRev
|
|
||||||
txtvRev = binding.txtvRev
|
|
||||||
butFF = binding.butFF
|
|
||||||
txtvFF = binding.txtvFF
|
|
||||||
butSkip = binding.butSkip
|
|
||||||
txtvSkip = binding.txtvSkip
|
|
||||||
sbPosition = binding.sbPosition
|
|
||||||
txtvPosition = binding.txtvPosition
|
|
||||||
txtvLength = binding.txtvLength
|
|
||||||
|
|
||||||
setupLengthTextView()
|
|
||||||
setupControlButtons()
|
|
||||||
butPlaybackSpeed.setOnClickListener {
|
|
||||||
VariableSpeedDialog().show(childFragmentManager, null)
|
|
||||||
}
|
|
||||||
sbPosition.setOnSeekBarChangeListener(this)
|
|
||||||
|
|
||||||
binding.externalPlayerFragment.setOnClickListener {
|
|
||||||
Log.d(TAG, "externalPlayerFragment was clicked")
|
|
||||||
val media = controller?.getMedia()
|
|
||||||
if (media != null) {
|
|
||||||
if (media.getMediaType() == MediaType.AUDIO) {
|
|
||||||
(activity as MainActivity).bottomSheet.setState(BottomSheetBehavior.STATE_EXPANDED)
|
|
||||||
} else {
|
|
||||||
val intent = getPlayerActivityIntent(requireContext(), media)
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
controller = setupPlaybackController()
|
|
||||||
controller!!.init()
|
|
||||||
EventBus.getDefault().register(this)
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(UnstableApi::class) override fun onDestroyView() {
|
|
||||||
super.onDestroyView()
|
|
||||||
_binding = null
|
|
||||||
controller?.release()
|
|
||||||
controller = null
|
|
||||||
EventBus.getDefault().unregister(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
@UnstableApi
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
butPlay.setOnClickListener {
|
|
||||||
if (controller == null) return@setOnClickListener
|
|
||||||
|
|
||||||
val media = controller!!.getMedia()
|
|
||||||
if (media?.getMediaType() == MediaType.VIDEO && controller!!.status != PlayerStatus.PLAYING) {
|
|
||||||
controller!!.playPause()
|
|
||||||
requireContext().startActivity(getPlayerActivityIntent(requireContext(), media))
|
|
||||||
} else {
|
|
||||||
controller!!.playPause()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loadMediaInfo()
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(UnstableApi::class) private fun setupControlButtons() {
|
|
||||||
butRev.setOnClickListener {
|
|
||||||
if (controller != null) {
|
|
||||||
val curr: Int = controller!!.position
|
|
||||||
controller!!.seekTo(curr - UserPreferences.rewindSecs * 1000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
butRev.setOnLongClickListener {
|
|
||||||
SkipPreferenceDialog.showSkipPreference(requireContext(),
|
|
||||||
SkipPreferenceDialog.SkipDirection.SKIP_REWIND, txtvRev)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
butPlay.setOnClickListener {
|
|
||||||
controller?.init()
|
|
||||||
controller?.playPause()
|
|
||||||
}
|
|
||||||
butPlay.setOnLongClickListener {
|
|
||||||
if (controller != null && controller!!.status == PlayerStatus.PLAYING) {
|
|
||||||
val fallbackSpeed = UserPreferences.fallbackSpeed
|
|
||||||
if (fallbackSpeed > 0.1f) controller!!.fallbackSpeed(fallbackSpeed)
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
butFF.setOnClickListener {
|
|
||||||
if (controller != null) {
|
|
||||||
val curr: Int = controller!!.position
|
|
||||||
controller!!.seekTo(curr + UserPreferences.fastForwardSecs * 1000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
butFF.setOnLongClickListener {
|
|
||||||
SkipPreferenceDialog.showSkipPreference(requireContext(),
|
|
||||||
SkipPreferenceDialog.SkipDirection.SKIP_FORWARD, txtvFF)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
butSkip.setOnClickListener {
|
|
||||||
if (controller != null && controller!!.status == PlayerStatus.PLAYING) {
|
|
||||||
val speedForward = UserPreferences.speedforwardSpeed
|
|
||||||
if (speedForward > 0.1f) controller!!.speedForward(speedForward)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
butSkip.setOnLongClickListener {
|
|
||||||
activity?.sendBroadcast(
|
|
||||||
MediaButtonReceiver.createIntent(requireContext(), KeyEvent.KEYCODE_MEDIA_NEXT))
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@UnstableApi
|
|
||||||
private fun setupPlaybackController(): PlaybackController {
|
|
||||||
return object : PlaybackController(requireActivity()) {
|
|
||||||
override fun updatePlayButtonShowsPlay(showPlay: Boolean) {
|
|
||||||
butPlay.setIsShowPlay(showPlay)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun loadMediaInfo() {
|
|
||||||
Log.d(TAG, "setupPlaybackController loadMediaInfo called")
|
|
||||||
this@ExternalPlayerFragment.loadMediaInfo()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPlaybackEnd() {
|
|
||||||
(activity as MainActivity).setPlayerVisible(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(UnstableApi::class) private fun setupLengthTextView() {
|
|
||||||
showTimeLeft = UserPreferences.shouldShowRemainingTime()
|
|
||||||
txtvLength.setOnClickListener(View.OnClickListener {
|
|
||||||
if (controller == null) {
|
|
||||||
return@OnClickListener
|
|
||||||
}
|
|
||||||
showTimeLeft = !showTimeLeft
|
|
||||||
UserPreferences.setShowRemainTimeSetting(showTimeLeft)
|
|
||||||
onPositionObserverUpdate(PlaybackPositionEvent(controller!!.position, controller!!.duration))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
|
||||||
fun updatePlaybackSpeedButton(event: SpeedChangedEvent) {
|
|
||||||
val speedStr: String = DecimalFormat("0.00").format(event.newSpeed.toDouble())
|
|
||||||
txtvPlaybackSpeed.text = speedStr
|
|
||||||
butPlaybackSpeed.setSpeed(event.newSpeed)
|
|
||||||
}
|
|
||||||
|
|
||||||
@UnstableApi
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
|
||||||
fun onPositionObserverUpdate(event: PlaybackPositionEvent) {
|
|
||||||
if (controller == null || controller!!.position == Playable.INVALID_TIME || controller!!.duration == Playable.INVALID_TIME) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val converter = TimeSpeedConverter(controller!!.currentPlaybackSpeedMultiplier)
|
|
||||||
val currentPosition: Int = converter.convert(event.position)
|
|
||||||
val duration: Int = converter.convert(event.duration)
|
|
||||||
val remainingTime: Int = converter.convert(max((event.duration - event.position).toDouble(), 0.0).toInt())
|
|
||||||
if (currentPosition == Playable.INVALID_TIME || duration == Playable.INVALID_TIME) {
|
|
||||||
Log.w(TAG, "Could not react to position observer update because of invalid time")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
txtvPosition.text = Converter.getDurationStringLong(currentPosition)
|
|
||||||
txtvPosition.setContentDescription(getString(R.string.position,
|
|
||||||
Converter.getDurationStringLocalized(requireContext(), currentPosition.toLong())))
|
|
||||||
val showTimeLeft = UserPreferences.shouldShowRemainingTime()
|
|
||||||
if (showTimeLeft) {
|
|
||||||
txtvLength.setContentDescription(getString(R.string.remaining_time,
|
|
||||||
Converter.getDurationStringLocalized(requireContext(), remainingTime.toLong())))
|
|
||||||
txtvLength.text = (if (remainingTime > 0) "-" else "") + Converter.getDurationStringLong(remainingTime)
|
|
||||||
} else {
|
|
||||||
txtvLength.setContentDescription(getString(R.string.chapter_duration,
|
|
||||||
Converter.getDurationStringLocalized(requireContext(), duration.toLong())))
|
|
||||||
txtvLength.text = Converter.getDurationStringLong(duration)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sbPosition.isPressed) {
|
|
||||||
val progress: Float = (event.position.toFloat()) / event.duration
|
|
||||||
sbPosition.progress = (progress * sbPosition.max).toInt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
|
|
||||||
fun onPlaybackServiceChanged(event: PlaybackServiceEvent) {
|
|
||||||
when (event.action) {
|
|
||||||
PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN -> {
|
|
||||||
(activity as MainActivity).setPlayerVisible(false)
|
|
||||||
}
|
|
||||||
PlaybackServiceEvent.Action.SERVICE_STARTED -> {
|
|
||||||
(activity as MainActivity).setPlayerVisible(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
super.onDestroy()
|
|
||||||
Log.d(TAG, "Fragment is about to be destroyed")
|
|
||||||
disposable?.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(UnstableApi::class) override fun onStart() {
|
|
||||||
super.onStart()
|
|
||||||
txtvRev.text = NumberFormat.getInstance().format(UserPreferences.rewindSecs.toLong())
|
|
||||||
txtvFF.text = NumberFormat.getInstance().format(UserPreferences.fastForwardSecs.toLong())
|
|
||||||
if (UserPreferences.speedforwardSpeed > 0.1f) {
|
|
||||||
txtvSkip.text = NumberFormat.getInstance().format(UserPreferences.speedforwardSpeed)
|
|
||||||
} else txtvSkip.visibility = View.GONE
|
|
||||||
val media = controller?.getMedia() ?: return
|
|
||||||
updatePlaybackSpeedButton(SpeedChangedEvent(PlaybackSpeedUtils.getCurrentPlaybackSpeed(media)))
|
|
||||||
}
|
|
||||||
|
|
||||||
@UnstableApi
|
|
||||||
override fun onPause() {
|
|
||||||
super.onPause()
|
|
||||||
controller?.pause()
|
|
||||||
}
|
|
||||||
|
|
||||||
@UnstableApi
|
|
||||||
private fun loadMediaInfo() {
|
|
||||||
Log.d(TAG, "loadMediaInfo called")
|
|
||||||
if (controller == null) {
|
|
||||||
Log.w(TAG, "loadMediaInfo was called while PlaybackController was null!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val theMedia = controller?.getMedia()
|
|
||||||
if (currentMedia == null || theMedia?.getIdentifier() != currentMedia?.getIdentifier()) {
|
|
||||||
Log.d(TAG, "reloading media info")
|
|
||||||
disposable?.dispose()
|
|
||||||
disposable = Maybe.fromCallable<Playable?> { theMedia }
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe({ media: Playable? ->
|
|
||||||
currentMedia = media
|
|
||||||
this.updateUi(media) },
|
|
||||||
{ error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) },
|
|
||||||
{
|
|
||||||
// (activity as MainActivity).setPlayerVisible(false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(UnstableApi::class) override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {}
|
|
||||||
|
|
||||||
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
|
||||||
|
|
||||||
@OptIn(UnstableApi::class) override fun onStopTrackingTouch(seekBar: SeekBar) {
|
|
||||||
if (controller != null) {
|
|
||||||
val prog: Float = seekBar.progress / (seekBar.max.toFloat())
|
|
||||||
controller!!.seekTo((prog * controller!!.duration).toInt())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@UnstableApi
|
|
||||||
private fun updateUi(media: Playable?) {
|
|
||||||
if (media == null) return
|
|
||||||
Log.d(TAG, "updateUi called")
|
|
||||||
|
|
||||||
episodeTitle.text = media.getEpisodeTitle()
|
|
||||||
(activity as MainActivity).setPlayerVisible(true)
|
|
||||||
onPositionObserverUpdate(PlaybackPositionEvent(media.getPosition(), media.getDuration()))
|
|
||||||
|
|
||||||
val options = RequestOptions()
|
|
||||||
.placeholder(R.color.light_gray)
|
|
||||||
.error(R.color.light_gray)
|
|
||||||
.fitCenter()
|
|
||||||
.dontAnimate()
|
|
||||||
|
|
||||||
val imgLoc = getEpisodeListImageLocation(media)
|
|
||||||
val imgLocFB = getFallbackImageLocation(media)
|
|
||||||
when {
|
|
||||||
!imgLoc.isNullOrBlank() -> Glide.with(this)
|
|
||||||
.load(imgLoc)
|
|
||||||
.apply(options)
|
|
||||||
.into(imgvCover)
|
|
||||||
!imgLocFB.isNullOrBlank() -> Glide.with(this)
|
|
||||||
.load(imgLocFB)
|
|
||||||
.apply(options)
|
|
||||||
.into(imgvCover)
|
|
||||||
else -> imgvCover.setImageResource(R.mipmap.ic_launcher)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (controller?.isPlayingVideoLocally == true) {
|
|
||||||
(activity as MainActivity).bottomSheet.setLocked(true)
|
|
||||||
(activity as MainActivity).bottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED)
|
|
||||||
} else {
|
|
||||||
butPlay.visibility = View.VISIBLE
|
|
||||||
(activity as MainActivity).bottomSheet.setLocked(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val TAG: String = "ExternalPlayerFragment"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -204,11 +204,9 @@ class FeedSettingsFragment : Fragment() {
|
||||||
val feedPlaybackSpeedPreference = findPreference<Preference>(PREF_FEED_PLAYBACK_SPEED)
|
val feedPlaybackSpeedPreference = findPreference<Preference>(PREF_FEED_PLAYBACK_SPEED)
|
||||||
feedPlaybackSpeedPreference!!.onPreferenceClickListener =
|
feedPlaybackSpeedPreference!!.onPreferenceClickListener =
|
||||||
Preference.OnPreferenceClickListener {
|
Preference.OnPreferenceClickListener {
|
||||||
val viewBinding =
|
val viewBinding = PlaybackSpeedFeedSettingDialogBinding.inflate(layoutInflater)
|
||||||
PlaybackSpeedFeedSettingDialogBinding.inflate(layoutInflater)
|
|
||||||
viewBinding.seekBar.setProgressChangedListener { speed: Float? ->
|
viewBinding.seekBar.setProgressChangedListener { speed: Float? ->
|
||||||
viewBinding.currentSpeedLabel.text = String.format(
|
viewBinding.currentSpeedLabel.text = String.format(Locale.getDefault(), "%.2fx", speed)
|
||||||
Locale.getDefault(), "%.2fx", speed)
|
|
||||||
}
|
}
|
||||||
viewBinding.useGlobalCheckbox.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
|
viewBinding.useGlobalCheckbox.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
|
||||||
viewBinding.seekBar.isEnabled = !isChecked
|
viewBinding.seekBar.isEnabled = !isChecked
|
||||||
|
@ -224,8 +222,8 @@ class FeedSettingsFragment : Fragment() {
|
||||||
.setTitle(R.string.playback_speed)
|
.setTitle(R.string.playback_speed)
|
||||||
.setView(viewBinding.root)
|
.setView(viewBinding.root)
|
||||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||||
val newSpeed = if (viewBinding.useGlobalCheckbox.isChecked
|
val newSpeed = if (viewBinding.useGlobalCheckbox.isChecked) FeedPreferences.SPEED_USE_GLOBAL
|
||||||
) FeedPreferences.SPEED_USE_GLOBAL else viewBinding.seekBar.currentSpeed
|
else viewBinding.seekBar.currentSpeed
|
||||||
feedPreferences!!.feedPlaybackSpeed = newSpeed
|
feedPreferences!!.feedPlaybackSpeed = newSpeed
|
||||||
if (feedPreferences != null) DBWriter.setFeedPreferences(feedPreferences!!)
|
if (feedPreferences != null) DBWriter.setFeedPreferences(feedPreferences!!)
|
||||||
EventBus.getDefault().post(
|
EventBus.getDefault().post(
|
||||||
|
|
|
@ -10,9 +10,7 @@ import ac.mdiq.podcini.playback.event.PlaybackPositionEvent
|
||||||
import ac.mdiq.podcini.preferences.UserPreferences
|
import ac.mdiq.podcini.preferences.UserPreferences
|
||||||
import ac.mdiq.podcini.storage.DBReader
|
import ac.mdiq.podcini.storage.DBReader
|
||||||
import ac.mdiq.podcini.storage.DBWriter
|
import ac.mdiq.podcini.storage.DBWriter
|
||||||
import ac.mdiq.podcini.storage.model.feed.FeedItem
|
import ac.mdiq.podcini.storage.model.feed.*
|
||||||
import ac.mdiq.podcini.storage.model.feed.FeedItemFilter
|
|
||||||
import ac.mdiq.podcini.storage.model.feed.SortOrder
|
|
||||||
import ac.mdiq.podcini.ui.activity.MainActivity
|
import ac.mdiq.podcini.ui.activity.MainActivity
|
||||||
import ac.mdiq.podcini.ui.adapter.QueueRecyclerAdapter
|
import ac.mdiq.podcini.ui.adapter.QueueRecyclerAdapter
|
||||||
import ac.mdiq.podcini.ui.adapter.SelectableAdapter
|
import ac.mdiq.podcini.ui.adapter.SelectableAdapter
|
||||||
|
@ -29,6 +27,7 @@ import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
|
||||||
import ac.mdiq.podcini.util.Converter
|
import ac.mdiq.podcini.util.Converter
|
||||||
import ac.mdiq.podcini.util.FeedItemUtil
|
import ac.mdiq.podcini.util.FeedItemUtil
|
||||||
import ac.mdiq.podcini.util.event.*
|
import ac.mdiq.podcini.util.event.*
|
||||||
|
import ac.mdiq.podcini.util.event.settings.SpeedPresetChangedEvent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
@ -311,6 +310,18 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
|
||||||
refreshToolbarState()
|
refreshToolbarState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
|
// @Suppress("unused")
|
||||||
|
// fun speedPresetChanged(event: SpeedPresetChangedEvent) {
|
||||||
|
//// Log.d(TAG,"speedPresetChanged called")
|
||||||
|
//// for (item in queue) {
|
||||||
|
//// if (item.feed?.id == event.feedId && item.feed!!.preferences != null) {
|
||||||
|
//// Log.d(TAG, "speedPresetChanged ${item.feed!!.title} ${event.speed}")
|
||||||
|
//// item.feed!!.preferences!!.feedPlaybackSpeed = event.speed
|
||||||
|
//// }
|
||||||
|
//// }
|
||||||
|
// }
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
fun onSwipeActionsChanged(event: SwipeActionsChangedEvent?) {
|
fun onSwipeActionsChanged(event: SwipeActionsChangedEvent?) {
|
||||||
refreshSwipeTelltale()
|
refreshSwipeTelltale()
|
||||||
|
|
|
@ -39,7 +39,7 @@ class PlaybackPreferencesFragment : PreferenceFragmentCompat() {
|
||||||
|
|
||||||
findPreference<Preference>(PREF_PLAYBACK_SPEED_LAUNCHER)!!.onPreferenceClickListener =
|
findPreference<Preference>(PREF_PLAYBACK_SPEED_LAUNCHER)!!.onPreferenceClickListener =
|
||||||
Preference.OnPreferenceClickListener {
|
Preference.OnPreferenceClickListener {
|
||||||
VariableSpeedDialog().show(childFragmentManager, null)
|
VariableSpeedDialog.newInstance("Global").show(childFragmentManager, null)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
findPreference<Preference>(PREF_PLAYBACK_REWIND_DELTA_LAUNCHER)!!.onPreferenceClickListener =
|
findPreference<Preference>(PREF_PLAYBACK_REWIND_DELTA_LAUNCHER)!!.onPreferenceClickListener =
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/playerFragment"
|
android:id="@+id/playerFragment1"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="top"
|
android:layout_gravity="top"
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
android:id="@+id/itemDescription"
|
android:id="@+id/itemDescription"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_above="@id/playtime_layout"
|
android:layout_above="@id/playerFragment2"
|
||||||
android:layout_below="@id/toolbar"
|
android:layout_below="@id/toolbar"
|
||||||
android:layout_marginBottom="12dp" />
|
android:layout_marginBottom="12dp" />
|
||||||
|
|
||||||
|
@ -75,231 +75,13 @@
|
||||||
tools:text="1:06:29" />
|
tools:text="1:06:29" />
|
||||||
</androidx.cardview.widget.CardView>
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
<LinearLayout
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/playtime_layout"
|
android:id="@+id/playerFragment2"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
android:layout_alignParentBottom="true"
|
android:layout_alignParentBottom="true"
|
||||||
android:layoutDirection="ltr"
|
tools:layout_height="@dimen/external_player_height" />
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/title_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="15dp"
|
|
||||||
android:gravity="start"
|
|
||||||
android:text="Title"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
|
||||||
android:textSize="15sp"/>
|
|
||||||
|
|
||||||
<ac.mdiq.podcini.ui.view.ChapterSeekBar
|
|
||||||
android:id="@+id/sbPosition"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginLeft="8dp"
|
|
||||||
android:layout_marginRight="8dp"
|
|
||||||
android:clickable="true"
|
|
||||||
android:max="500"
|
|
||||||
tools:progress="100"/>
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:layout_marginBottom="4dp"
|
|
||||||
android:paddingLeft="8dp"
|
|
||||||
android:paddingRight="8dp">
|
|
||||||
|
|
||||||
<ac.mdiq.podcini.ui.view.NoRelayoutTextView
|
|
||||||
android:id="@+id/txtvPosition"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginLeft="16dp"
|
|
||||||
android:text="@string/position_default_label"
|
|
||||||
android:textColor="?android:attr/textColorSecondary"
|
|
||||||
android:textSize="@dimen/text_size_micro"/>
|
|
||||||
|
|
||||||
<ac.mdiq.podcini.ui.view.NoRelayoutTextView
|
|
||||||
android:id="@+id/txtvLength"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_alignParentRight="true"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginRight="16dp"
|
|
||||||
android:textAlignment="textEnd"
|
|
||||||
android:background="?android:attr/selectableItemBackground"
|
|
||||||
android:text="@string/position_default_label"
|
|
||||||
android:textColor="?android:attr/textColorSecondary"
|
|
||||||
android:textSize="@dimen/text_size_micro"/>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:id="@+id/player_control"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="24dp">
|
|
||||||
|
|
||||||
<ac.mdiq.podcini.ui.view.PlayButton
|
|
||||||
android:id="@+id/butPlay"
|
|
||||||
android:layout_width="@dimen/audioplayer_playercontrols_length_big"
|
|
||||||
android:layout_height="@dimen/audioplayer_playercontrols_length_big"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:layout_marginStart="@dimen/audioplayer_playercontrols_margin"
|
|
||||||
android:layout_marginLeft="@dimen/audioplayer_playercontrols_margin"
|
|
||||||
android:layout_marginEnd="@dimen/audioplayer_playercontrols_margin"
|
|
||||||
android:layout_marginRight="@dimen/audioplayer_playercontrols_margin"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:contentDescription="@string/pause_label"
|
|
||||||
android:padding="8dp"
|
|
||||||
android:scaleType="fitCenter"
|
|
||||||
app:srcCompat="@drawable/ic_play_48dp"
|
|
||||||
tools:srcCompat="@drawable/ic_play_48dp" />
|
|
||||||
|
|
||||||
<ac.mdiq.podcini.ui.common.CircularProgressBar
|
|
||||||
android:layout_width="@dimen/audioplayer_playercontrols_length_big"
|
|
||||||
android:layout_height="@dimen/audioplayer_playercontrols_length_big"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginLeft="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginRight="16dp"
|
|
||||||
app:foregroundColor="?attr/action_icon_color" />
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/progLoading"
|
|
||||||
android:layout_width="@dimen/audioplayer_playercontrols_length_big"
|
|
||||||
android:layout_height="@dimen/audioplayer_playercontrols_length_big"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:visibility="gone"
|
|
||||||
style="?android:attr/progressBarStyle" />
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/butRev"
|
|
||||||
android:layout_width="@dimen/audioplayer_playercontrols_length"
|
|
||||||
android:layout_height="@dimen/audioplayer_playercontrols_length"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:layout_marginStart="@dimen/audioplayer_playercontrols_margin"
|
|
||||||
android:layout_marginLeft="@dimen/audioplayer_playercontrols_margin"
|
|
||||||
android:layout_toStartOf="@id/butPlay"
|
|
||||||
android:layout_toLeftOf="@id/butPlay"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:contentDescription="@string/rewind_label"
|
|
||||||
android:scaleType="fitCenter"
|
|
||||||
app:srcCompat="@drawable/ic_fast_rewind"
|
|
||||||
tools:srcCompat="@drawable/ic_fast_rewind" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/txtvRev"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@id/butRev"
|
|
||||||
android:layout_alignStart="@id/butRev"
|
|
||||||
android:layout_alignLeft="@id/butRev"
|
|
||||||
android:layout_alignEnd="@id/butRev"
|
|
||||||
android:layout_alignRight="@id/butRev"
|
|
||||||
android:clickable="false"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="30"
|
|
||||||
android:textColor="?android:attr/textColorSecondary"
|
|
||||||
android:textSize="12sp" />
|
|
||||||
|
|
||||||
<ac.mdiq.podcini.ui.common.PlaybackSpeedIndicatorView
|
|
||||||
android:id="@+id/butPlaybackSpeed"
|
|
||||||
android:layout_width="@dimen/audioplayer_playercontrols_length"
|
|
||||||
android:layout_height="@dimen/audioplayer_playercontrols_length"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:layout_toStartOf="@id/butRev"
|
|
||||||
android:layout_toLeftOf="@id/butRev"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:contentDescription="@string/playback_speed"
|
|
||||||
app:foregroundColor="?attr/action_icon_color"
|
|
||||||
tools:srcCompat="@drawable/ic_playback_speed" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/txtvPlaybackSpeed"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@id/butPlaybackSpeed"
|
|
||||||
android:layout_alignStart="@id/butPlaybackSpeed"
|
|
||||||
android:layout_alignLeft="@id/butPlaybackSpeed"
|
|
||||||
android:layout_alignEnd="@id/butPlaybackSpeed"
|
|
||||||
android:layout_alignRight="@id/butPlaybackSpeed"
|
|
||||||
android:clickable="false"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="1.00"
|
|
||||||
android:textColor="?android:attr/textColorSecondary"
|
|
||||||
android:textSize="12sp" />
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/butFF"
|
|
||||||
android:layout_width="@dimen/audioplayer_playercontrols_length"
|
|
||||||
android:layout_height="@dimen/audioplayer_playercontrols_length"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:layout_marginEnd="@dimen/audioplayer_playercontrols_margin"
|
|
||||||
android:layout_marginRight="@dimen/audioplayer_playercontrols_margin"
|
|
||||||
android:layout_toEndOf="@id/butPlay"
|
|
||||||
android:layout_toRightOf="@id/butPlay"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:contentDescription="@string/fast_forward_label"
|
|
||||||
android:scaleType="fitCenter"
|
|
||||||
app:srcCompat="@drawable/ic_fast_forward"
|
|
||||||
tools:srcCompat="@drawable/ic_fast_forward" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/txtvFF"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@id/butFF"
|
|
||||||
android:layout_alignStart="@id/butFF"
|
|
||||||
android:layout_alignLeft="@id/butFF"
|
|
||||||
android:layout_alignEnd="@id/butFF"
|
|
||||||
android:layout_alignRight="@id/butFF"
|
|
||||||
android:clickable="false"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="30"
|
|
||||||
android:textColor="?android:attr/textColorSecondary"
|
|
||||||
android:textSize="12sp" />
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/butSkip"
|
|
||||||
android:layout_width="@dimen/audioplayer_playercontrols_length"
|
|
||||||
android:layout_height="@dimen/audioplayer_playercontrols_length"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:layout_toEndOf="@id/butFF"
|
|
||||||
android:layout_toRightOf="@id/butFF"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:contentDescription="@string/skip_episode_label"
|
|
||||||
android:scaleType="fitCenter"
|
|
||||||
app:srcCompat="@drawable/ic_skip_48dp"
|
|
||||||
tools:srcCompat="@drawable/ic_skip_48dp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/txtvSkip"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@id/butSkip"
|
|
||||||
android:layout_alignStart="@id/butSkip"
|
|
||||||
android:layout_alignLeft="@id/butSkip"
|
|
||||||
android:layout_alignEnd="@id/butSkip"
|
|
||||||
android:layout_alignRight="@id/butSkip"
|
|
||||||
android:clickable="false"
|
|
||||||
android:gravity="center"
|
|
||||||
android:textColor="?android:attr/textColorSecondary"
|
|
||||||
android:textSize="12sp" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/external_player_fragment"
|
android:id="@+id/internal_player_fragment"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/external_player_height"
|
android:layout_height="@dimen/external_player_height"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
android:id="@+id/sbPosition"
|
android:id="@+id/sbPosition"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="5.dp"
|
android:layout_marginTop="5dp"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:max="500"
|
android:max="500"
|
||||||
tools:progress="100" />
|
tools:progress="100" />
|
||||||
|
@ -126,6 +126,7 @@
|
||||||
android:layout_alignLeft="@id/butRev"
|
android:layout_alignLeft="@id/butRev"
|
||||||
android:layout_alignEnd="@id/butRev"
|
android:layout_alignEnd="@id/butRev"
|
||||||
android:layout_alignRight="@id/butRev"
|
android:layout_alignRight="@id/butRev"
|
||||||
|
android:layout_marginBottom="5dp"
|
||||||
android:clickable="false"
|
android:clickable="false"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:text="30"
|
android:text="30"
|
||||||
|
@ -155,6 +156,7 @@
|
||||||
android:layout_alignLeft="@id/butPlaybackSpeed"
|
android:layout_alignLeft="@id/butPlaybackSpeed"
|
||||||
android:layout_alignEnd="@id/butPlaybackSpeed"
|
android:layout_alignEnd="@id/butPlaybackSpeed"
|
||||||
android:layout_alignRight="@id/butPlaybackSpeed"
|
android:layout_alignRight="@id/butPlaybackSpeed"
|
||||||
|
android:layout_marginBottom="5dp"
|
||||||
android:clickable="false"
|
android:clickable="false"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:text="1.00"
|
android:text="1.00"
|
||||||
|
@ -186,6 +188,7 @@
|
||||||
android:layout_alignLeft="@id/butFF"
|
android:layout_alignLeft="@id/butFF"
|
||||||
android:layout_alignEnd="@id/butFF"
|
android:layout_alignEnd="@id/butFF"
|
||||||
android:layout_alignRight="@id/butFF"
|
android:layout_alignRight="@id/butFF"
|
||||||
|
android:layout_marginBottom="5dp"
|
||||||
android:clickable="false"
|
android:clickable="false"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:text="30"
|
android:text="30"
|
||||||
|
@ -217,6 +220,7 @@
|
||||||
android:layout_alignLeft="@id/butSkip"
|
android:layout_alignLeft="@id/butSkip"
|
||||||
android:layout_alignEnd="@id/butSkip"
|
android:layout_alignEnd="@id/butSkip"
|
||||||
android:layout_alignRight="@id/butSkip"
|
android:layout_alignRight="@id/butSkip"
|
||||||
|
android:layout_marginBottom="5dp"
|
||||||
android:clickable="false"
|
android:clickable="false"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:textColor="?android:attr/textColorSecondary"
|
android:textColor="?android:attr/textColorSecondary"
|
|
@ -58,12 +58,33 @@
|
||||||
android:text="@string/All" />
|
android:text="@string/All" />
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="8dp"
|
android:orientation="horizontal">
|
||||||
android:text="@string/speed_presets"
|
|
||||||
style="@style/Podcini.TextView.ListItemPrimaryTitle" />
|
<CheckBox
|
||||||
|
android:id="@+id/currentAudio"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:checked="true"
|
||||||
|
android:text="@string/current_episode" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/currentPodcast"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/current_podcast" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/global"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/global" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/selected_speeds_grid"
|
android:id="@+id/selected_speeds_grid"
|
||||||
|
|
|
@ -27,6 +27,9 @@
|
||||||
<string name="years_statistics_label">Years</string>
|
<string name="years_statistics_label">Years</string>
|
||||||
<string name="notification_pref_fragment">Notifications</string>
|
<string name="notification_pref_fragment">Notifications</string>
|
||||||
<string name="current_playing_episode">Current</string>
|
<string name="current_playing_episode">Current</string>
|
||||||
|
<string name="current_episode">Current</string>
|
||||||
|
<string name="current_podcast">Podcast</string>
|
||||||
|
<string name="global">Global</string>
|
||||||
<string name="podcini_echo" translatable="false">Podcini Echo</string>
|
<string name="podcini_echo" translatable="false">Podcini Echo</string>
|
||||||
<string name="podcini_echo_year" translatable="false">Podcini Echo %d</string>
|
<string name="podcini_echo_year" translatable="false">Podcini Echo %d</string>
|
||||||
|
|
||||||
|
|
|
@ -203,3 +203,9 @@
|
||||||
## 4.5.1
|
## 4.5.1
|
||||||
|
|
||||||
* fixed bug in subscription sorting
|
* fixed bug in subscription sorting
|
||||||
|
|
||||||
|
## 4.5.2
|
||||||
|
|
||||||
|
* revamped audio player class, merged external player in
|
||||||
|
* speed setting now allows setting with three options: current audio, podcast, and global.
|
||||||
|
* added a bit bottom margin for the numbers in player
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
Version 4.5.2 brings several changes:
|
||||||
|
|
||||||
|
* revamped audio player class, merged external player in
|
||||||
|
* speed setting now allows setting with three options: current audio, podcast, and global.
|
||||||
|
* added a bit bottom margin for the numbers in player
|
Loading…
Reference in New Issue