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 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:
|
||||
* click on title area opens the podcast/episode
|
||||
* 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
|
||||
* Played episodes have clearer markings
|
||||
* Sort dialog no longer dims the main view
|
||||
* Play speed setting has been straightened up, three places to set the play speed:
|
||||
* global setting at the preference
|
||||
* 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
|
||||
* Play speed setting has been straightened up, three speed can be set separately or combined: current audio, podcast, and global
|
||||
* customized podcast speed 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)
|
||||
* The "Skip to next episode" button on the player
|
||||
* long-press moves to the next episode
|
||||
|
|
|
@ -149,8 +149,8 @@ android {
|
|||
// Version code schema (not used):
|
||||
// "1.2.3-beta4" -> 1020304
|
||||
// "1.2.3" -> 1020395
|
||||
versionCode 3020121
|
||||
versionName "4.5.1"
|
||||
versionCode 3020122
|
||||
versionName "4.5.2"
|
||||
|
||||
def commit = ""
|
||||
try {
|
||||
|
|
|
@ -33,6 +33,7 @@ object PlaybackSpeedUtils {
|
|||
val feed = item.feed
|
||||
if (feed?.preferences != null) {
|
||||
playbackSpeed = feed.preferences!!.feedPlaybackSpeed
|
||||
Log.d(TAG, "using feed speed $playbackSpeed")
|
||||
} else {
|
||||
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() {
|
||||
try {
|
||||
activity.unbindService(mConnection)
|
||||
|
@ -338,9 +342,9 @@ abstract class PlaybackController(private val activity: FragmentActivity) {
|
|||
playbackService?.setVideoSurface(holder)
|
||||
}
|
||||
|
||||
fun setPlaybackSpeed(speed: Float) {
|
||||
fun setPlaybackSpeed(speed: Float, codeArray: Array<Int>? = null) {
|
||||
if (playbackService != null) {
|
||||
playbackService!!.setSpeed(speed)
|
||||
playbackService!!.setSpeed(speed, codeArray)
|
||||
} else {
|
||||
EventBus.getDefault().post(SpeedChangedEvent(speed))
|
||||
}
|
||||
|
|
|
@ -1617,15 +1617,41 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
|||
val playable: Playable?
|
||||
get() = mediaPlayer?.getPlayable()
|
||||
|
||||
fun setSpeed(speed: Float) {
|
||||
fun setSpeed(speed: Float, codeArray: Array<Int>? = null) {
|
||||
isSpeedForward = false
|
||||
isFallbackSpeed = false
|
||||
|
||||
currentlyPlayingTemporaryPlaybackSpeed = speed
|
||||
|
||||
if (currentMediaType == MediaType.VIDEO) {
|
||||
videoPlaybackSpeed = speed
|
||||
} 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)
|
||||
|
|
|
@ -340,7 +340,9 @@ class MainActivity : CastEnabledActivity() {
|
|||
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)
|
||||
if (visible) {
|
||||
bottomSheetCallback.onStateChanged(dummyView, bottomSheet.state) // Update toolbar visibility
|
||||
|
@ -353,7 +355,7 @@ class MainActivity : CastEnabledActivity() {
|
|||
params.setMargins(navigationBarInsets.left, 0, navigationBarInsets.right,
|
||||
navigationBarInsets.bottom + (if (visible) externalPlayerHeight else 0))
|
||||
mainView.layoutParams = params
|
||||
val playerView = findViewById<FragmentContainerView>(R.id.playerFragment)
|
||||
val playerView = findViewById<FragmentContainerView>(R.id.playerFragment1)
|
||||
val playerParams = playerView.layoutParams as MarginLayoutParams
|
||||
playerParams.setMargins(navigationBarInsets.left, 0, navigationBarInsets.right, 0)
|
||||
playerView.layoutParams = playerParams
|
||||
|
|
|
@ -15,6 +15,7 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.CompoundButton
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
@ -28,6 +29,7 @@ import java.text.DecimalFormatSymbols
|
|||
import java.util.*
|
||||
|
||||
open class VariableSpeedDialog : BottomSheetDialogFragment() {
|
||||
|
||||
private lateinit var adapter: SpeedSelectionAdapter
|
||||
private lateinit var speedSeekBar: PlaybackSpeedSeekBar
|
||||
private lateinit var addCurrentSpeedChip: Chip
|
||||
|
@ -38,6 +40,8 @@ open class VariableSpeedDialog : BottomSheetDialogFragment() {
|
|||
private var controller: PlaybackController? = null
|
||||
private val selectedSpeeds: MutableList<Float>
|
||||
|
||||
private val settingCode: Array<Int> = Array(3) { 0 }
|
||||
|
||||
init {
|
||||
val format = DecimalFormatSymbols(Locale.US)
|
||||
format.decimalSeparator = '.'
|
||||
|
@ -46,12 +50,14 @@ open class VariableSpeedDialog : BottomSheetDialogFragment() {
|
|||
|
||||
@UnstableApi override fun onStart() {
|
||||
super.onStart()
|
||||
if (controller == null) {
|
||||
controller = object : PlaybackController(requireActivity()) {
|
||||
override fun loadMediaInfo() {
|
||||
if (controller != null) updateSpeed(SpeedChangedEvent(controller!!.currentPlaybackSpeedMultiplier))
|
||||
}
|
||||
}
|
||||
controller?.init()
|
||||
}
|
||||
EventBus.getDefault().register(this)
|
||||
if (controller != null) updateSpeed(SpeedChangedEvent(controller!!.currentPlaybackSpeedMultiplier))
|
||||
}
|
||||
|
@ -73,6 +79,21 @@ open class VariableSpeedDialog : BottomSheetDialogFragment() {
|
|||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
_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.setProgressChangedListener { multiplier: Float ->
|
||||
controller?.setPlaybackSpeed(multiplier)
|
||||
|
@ -95,15 +116,24 @@ open class VariableSpeedDialog : BottomSheetDialogFragment() {
|
|||
skipSilence.isChecked = isSkipSilence
|
||||
skipSilence.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
|
||||
isSkipSilence = isChecked
|
||||
controller!!.setSkipSilence(isChecked)
|
||||
controller?.setSkipSilence(isChecked)
|
||||
}
|
||||
|
||||
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() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
private fun addCurrentSpeed() {
|
||||
val newSpeed = speedSeekBar.currentSpeed
|
||||
if (selectedSpeeds.contains(newSpeed)) {
|
||||
|
@ -134,15 +164,16 @@ open class VariableSpeedDialog : BottomSheetDialogFragment() {
|
|||
notifyDataSetChanged()
|
||||
true
|
||||
}
|
||||
holder.chip.setOnClickListener {
|
||||
Handler(Looper.getMainLooper()).postDelayed(
|
||||
{
|
||||
holder.chip.setOnClickListener { Handler(Looper.getMainLooper()).postDelayed({
|
||||
if (binding.currentAudio.isChecked) settingCode[0] = 1
|
||||
if (binding.currentPodcast.isChecked) settingCode[1] = 1
|
||||
if (binding.global.isChecked) settingCode[2] = 1
|
||||
|
||||
if (controller != null) {
|
||||
dismiss()
|
||||
controller!!.setPlaybackSpeed(speed)
|
||||
}
|
||||
}, 200)
|
||||
controller!!.setPlaybackSpeed(speed, settingCode)
|
||||
}
|
||||
}, 200) }
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
|
@ -156,4 +187,16 @@ open class VariableSpeedDialog : BottomSheetDialogFragment() {
|
|||
inner class ViewHolder internal constructor(var chip: Chip) : RecyclerView.ViewHolder(
|
||||
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.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.playback.PlaybackController
|
||||
import ac.mdiq.podcini.playback.base.PlayerStatus
|
||||
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.receiver.MediaButtonReceiver
|
||||
import ac.mdiq.podcini.service.playback.PlaybackService
|
||||
import ac.mdiq.podcini.storage.model.feed.Chapter
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedItem
|
||||
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.ui.activity.MainActivity
|
||||
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.event.FavoritesEvent
|
||||
import ac.mdiq.podcini.util.event.PlayerErrorEvent
|
||||
import ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
|
@ -37,15 +43,18 @@ import android.text.Html
|
|||
import android.util.Log
|
||||
import android.view.*
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.ImageView
|
||||
import android.widget.SeekBar
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.core.app.ShareCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||
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.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.elevation.SurfaceColors
|
||||
|
@ -70,35 +79,25 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
var _binding: AudioplayerFragmentBinding? = null
|
||||
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 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 txtvSeek: TextView
|
||||
|
||||
private var controller: PlaybackController? = null
|
||||
private var disposable: Disposable? = null
|
||||
private var showTimeLeft = false
|
||||
private var seekedToChapterStart = false
|
||||
private var currentChapterIndex = -1
|
||||
private var duration = 0
|
||||
|
||||
private var currentMedia: Playable? = null
|
||||
|
||||
@SuppressLint("WrongConstant")
|
||||
override fun onCreateView(inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
@ -120,50 +119,39 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
}
|
||||
toolbar.setOnMenuItemClickListener(this)
|
||||
|
||||
val externalPlayerFragment = ExternalPlayerFragment()
|
||||
controller = newPlaybackController()
|
||||
controller!!.init()
|
||||
|
||||
playerFragment1 = InternalPlayerFragment.newInstance(controller!!)
|
||||
childFragmentManager.beginTransaction()
|
||||
.replace(R.id.playerFragment, externalPlayerFragment, ExternalPlayerFragment.TAG)
|
||||
.replace(R.id.playerFragment1, playerFragment1!!, InternalPlayerFragment.TAG)
|
||||
.commit()
|
||||
// playerFragment = binding.playerFragment
|
||||
playerFragment = binding.root.findViewById(R.id.playerFragment)
|
||||
playerFragment.setBackgroundColor(
|
||||
playerView1 = binding.root.findViewById(R.id.playerFragment1)
|
||||
playerView1.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))
|
||||
|
||||
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
|
||||
txtvSeek = binding.txtvSeek
|
||||
|
||||
setupLengthTextView()
|
||||
setupControlButtons()
|
||||
butPlaybackSpeed.setOnClickListener {
|
||||
VariableSpeedDialog().show(childFragmentManager, null)
|
||||
}
|
||||
sbPosition.setOnSeekBarChangeListener(this)
|
||||
|
||||
val fm = requireActivity().supportFragmentManager
|
||||
val transaction = fm.beginTransaction()
|
||||
val itemDescFrag = PlayerDetailsFragment()
|
||||
transaction.replace(R.id.itemDescription, itemDescFrag).commit()
|
||||
|
||||
controller = newPlaybackController()
|
||||
controller?.init()
|
||||
loadMediaInfo(false)
|
||||
// controller = externalPlayerFragment1.controller
|
||||
// loadMediaInfo(false)
|
||||
EventBus.getDefault().register(this)
|
||||
|
||||
// updateUi(controller?.getMedia())
|
||||
return binding.root
|
||||
}
|
||||
|
||||
|
@ -189,62 +177,13 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
dividerPos[i] = chapters[i].start / duration.toFloat()
|
||||
}
|
||||
}
|
||||
|
||||
sbPosition.setDividerPos(dividerPos)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onUnreadItemsUpdate(event: UnreadItemsUpdateEvent?) {
|
||||
if (controller == null) return
|
||||
updatePosition(PlaybackPositionEvent(controller!!.position, controller!!.duration))
|
||||
}
|
||||
// @Subscribe(threadMode = ThreadMode.MAIN)
|
||||
// fun onUnreadItemsUpdate(event: UnreadItemsUpdateEvent?) {
|
||||
// if (controller == null) return
|
||||
// updatePosition(PlaybackPositionEvent(controller!!.position, controller!!.duration))
|
||||
// }
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onPlaybackServiceChanged(event: PlaybackServiceEvent) {
|
||||
|
@ -253,29 +192,33 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
}
|
||||
}
|
||||
|
||||
private fun setupLengthTextView() {
|
||||
showTimeLeft = UserPreferences.shouldShowRemainingTime()
|
||||
txtvLength.setOnClickListener(View.OnClickListener {
|
||||
if (controller == null) return@OnClickListener
|
||||
// private fun setupLengthTextView() {
|
||||
// showTimeLeft = UserPreferences.shouldShowRemainingTime()
|
||||
// txtvLength.setOnClickListener(View.OnClickListener {
|
||||
// if (controller == null) return@OnClickListener
|
||||
//
|
||||
// showTimeLeft = !showTimeLeft
|
||||
// UserPreferences.setShowRemainTimeSetting(showTimeLeft)
|
||||
// updatePosition(PlaybackPositionEvent(controller!!.position, controller!!.duration))
|
||||
// })
|
||||
// }
|
||||
|
||||
showTimeLeft = !showTimeLeft
|
||||
UserPreferences.setShowRemainTimeSetting(showTimeLeft)
|
||||
updatePosition(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)
|
||||
}
|
||||
// @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) {
|
||||
Log.d(TAG, "loadMediaInfo called")
|
||||
|
||||
val theMedia = controller?.getMedia() ?: return
|
||||
if (currentMedia == null || theMedia?.getIdentifier() != currentMedia?.getIdentifier()) {
|
||||
Log.d(TAG, "loadMediaInfo loading details")
|
||||
disposable?.dispose()
|
||||
disposable = Maybe.create<Playable> { emitter: MaybeEmitter<Playable?> ->
|
||||
val media: Playable? = controller?.getMedia()
|
||||
val media: Playable? = theMedia
|
||||
if (media != null) {
|
||||
if (includingChapters) {
|
||||
ChapterUtils.loadChapters(media, requireContext(), false)
|
||||
|
@ -288,18 +231,23 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
.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) })
|
||||
}
|
||||
}
|
||||
|
||||
private fun newPlaybackController(): PlaybackController {
|
||||
return object : PlaybackController(requireActivity()) {
|
||||
override fun updatePlayButtonShowsPlay(showPlay: Boolean) {
|
||||
butPlay.setIsShowPlay(showPlay)
|
||||
playerFragment1?.butPlay?.setIsShowPlay(showPlay)
|
||||
playerFragment2?.butPlay?.setIsShowPlay(showPlay)
|
||||
}
|
||||
|
||||
override fun loadMediaInfo() {
|
||||
|
@ -307,19 +255,15 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
}
|
||||
|
||||
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?) {
|
||||
if (controller != null) duration = controller!!.duration
|
||||
if (media == null) return
|
||||
Log.d(TAG, "updateUi called")
|
||||
|
||||
episodeTitle.text = media.getEpisodeTitle()
|
||||
updatePosition(PlaybackPositionEvent(media.getPosition(), media.getDuration()))
|
||||
updatePlaybackSpeedButton(SpeedChangedEvent(PlaybackSpeedUtils.getCurrentPlaybackSpeed(media)))
|
||||
setChapterDividers(media)
|
||||
setupOptionsMenu(media)
|
||||
}
|
||||
|
@ -339,73 +283,33 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
txtvRev.text = NumberFormat.getInstance().format(UserPreferences.rewindSecs.toLong())
|
||||
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
|
||||
loadMediaInfo(false)
|
||||
}
|
||||
|
||||
override fun 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()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
@Suppress("unused")
|
||||
fun bufferUpdate(event: BufferUpdateEvent) {
|
||||
when {
|
||||
event.hasStarted() -> {
|
||||
progressIndicator.visibility = View.VISIBLE
|
||||
}
|
||||
event.hasEnded() -> {
|
||||
progressIndicator.visibility = View.GONE
|
||||
}
|
||||
controller != null && controller!!.isStreaming -> {
|
||||
sbPosition.setSecondaryProgress((event.progress * sbPosition.max).toInt())
|
||||
}
|
||||
else -> {
|
||||
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)
|
||||
// @Suppress("unused")
|
||||
// fun bufferUpdate(event: BufferUpdateEvent) {
|
||||
// when {
|
||||
// event.hasStarted() -> {
|
||||
// progressIndicator.visibility = View.VISIBLE
|
||||
// }
|
||||
// event.hasEnded() -> {
|
||||
// progressIndicator.visibility = View.GONE
|
||||
// }
|
||||
//// controller != null && controller!!.isStreaming -> {
|
||||
//// sbPosition.setSecondaryProgress((event.progress * sbPosition.max).toInt())
|
||||
//// }
|
||||
// else -> {
|
||||
//// sbPosition.setSecondaryProgress(0)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun favoritesChanged(event: FavoritesEvent?) {
|
||||
|
@ -427,15 +331,15 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
var position: Int = converter.convert((prog * controller!!.duration).toInt())
|
||||
val newChapterIndex: Int = ChapterUtils.getCurrentChapterIndex(controller!!.getMedia(), position)
|
||||
if (newChapterIndex > -1) {
|
||||
if (!sbPosition.isPressed && currentChapterIndex != newChapterIndex) {
|
||||
currentChapterIndex = newChapterIndex
|
||||
val media = controller!!.getMedia()
|
||||
position = media?.getChapters()?.get(currentChapterIndex)?.start?.toInt() ?: 0
|
||||
seekedToChapterStart = true
|
||||
controller!!.seekTo(position)
|
||||
updateUi(controller!!.getMedia())
|
||||
sbPosition.highlightCurrentChapter()
|
||||
}
|
||||
// if (!sbPosition.isPressed && currentChapterIndex != newChapterIndex) {
|
||||
// currentChapterIndex = newChapterIndex
|
||||
// val media = controller!!.getMedia()
|
||||
// position = media?.getChapters()?.get(currentChapterIndex)?.start?.toInt() ?: 0
|
||||
// seekedToChapterStart = true
|
||||
// controller!!.seekTo(position)
|
||||
// updateUi(controller!!.getMedia())
|
||||
// sbPosition.highlightCurrentChapter()
|
||||
// }
|
||||
txtvSeek.text = controller!!.getMedia()?.getChapters()?.get(newChapterIndex)?.title ?: (""
|
||||
+ "\n" + Converter.getDurationStringLong(position))
|
||||
} else {
|
||||
|
@ -538,7 +442,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
|
||||
fun fadePlayerToToolbar(slideOffset: Float) {
|
||||
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.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()
|
||||
|
@ -546,6 +450,297 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
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 {
|
||||
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)
|
||||
feedPlaybackSpeedPreference!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
val viewBinding =
|
||||
PlaybackSpeedFeedSettingDialogBinding.inflate(layoutInflater)
|
||||
val viewBinding = PlaybackSpeedFeedSettingDialogBinding.inflate(layoutInflater)
|
||||
viewBinding.seekBar.setProgressChangedListener { speed: Float? ->
|
||||
viewBinding.currentSpeedLabel.text = String.format(
|
||||
Locale.getDefault(), "%.2fx", speed)
|
||||
viewBinding.currentSpeedLabel.text = String.format(Locale.getDefault(), "%.2fx", speed)
|
||||
}
|
||||
viewBinding.useGlobalCheckbox.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
|
||||
viewBinding.seekBar.isEnabled = !isChecked
|
||||
|
@ -224,8 +222,8 @@ class FeedSettingsFragment : Fragment() {
|
|||
.setTitle(R.string.playback_speed)
|
||||
.setView(viewBinding.root)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||
val newSpeed = if (viewBinding.useGlobalCheckbox.isChecked
|
||||
) FeedPreferences.SPEED_USE_GLOBAL else viewBinding.seekBar.currentSpeed
|
||||
val newSpeed = if (viewBinding.useGlobalCheckbox.isChecked) FeedPreferences.SPEED_USE_GLOBAL
|
||||
else viewBinding.seekBar.currentSpeed
|
||||
feedPreferences!!.feedPlaybackSpeed = newSpeed
|
||||
if (feedPreferences != null) DBWriter.setFeedPreferences(feedPreferences!!)
|
||||
EventBus.getDefault().post(
|
||||
|
|
|
@ -10,9 +10,7 @@ import ac.mdiq.podcini.playback.event.PlaybackPositionEvent
|
|||
import ac.mdiq.podcini.preferences.UserPreferences
|
||||
import ac.mdiq.podcini.storage.DBReader
|
||||
import ac.mdiq.podcini.storage.DBWriter
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedItem
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedItemFilter
|
||||
import ac.mdiq.podcini.storage.model.feed.SortOrder
|
||||
import ac.mdiq.podcini.storage.model.feed.*
|
||||
import ac.mdiq.podcini.ui.activity.MainActivity
|
||||
import ac.mdiq.podcini.ui.adapter.QueueRecyclerAdapter
|
||||
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.FeedItemUtil
|
||||
import ac.mdiq.podcini.util.event.*
|
||||
import ac.mdiq.podcini.util.event.settings.SpeedPresetChangedEvent
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.SharedPreferences
|
||||
|
@ -311,6 +310,18 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
|
|||
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)
|
||||
fun onSwipeActionsChanged(event: SwipeActionsChangedEvent?) {
|
||||
refreshSwipeTelltale()
|
||||
|
|
|
@ -39,7 +39,7 @@ class PlaybackPreferencesFragment : PreferenceFragmentCompat() {
|
|||
|
||||
findPreference<Preference>(PREF_PLAYBACK_SPEED_LAUNCHER)!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
VariableSpeedDialog().show(childFragmentManager, null)
|
||||
VariableSpeedDialog.newInstance("Global").show(childFragmentManager, null)
|
||||
true
|
||||
}
|
||||
findPreference<Preference>(PREF_PLAYBACK_REWIND_DELTA_LAUNCHER)!!.onPreferenceClickListener =
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/playerFragment"
|
||||
android:id="@+id/playerFragment1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top"
|
||||
|
@ -35,7 +35,7 @@
|
|||
android:id="@+id/itemDescription"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_above="@id/playtime_layout"
|
||||
android:layout_above="@id/playerFragment2"
|
||||
android:layout_below="@id/toolbar"
|
||||
android:layout_marginBottom="12dp" />
|
||||
|
||||
|
@ -75,231 +75,13 @@
|
|||
tools:text="1:06:29" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/playtime_layout"
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/playerFragment2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layoutDirection="ltr"
|
||||
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>
|
||||
tools:layout_height="@dimen/external_player_height" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
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_height="@dimen/external_player_height"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
|
@ -25,7 +25,7 @@
|
|||
android:id="@+id/sbPosition"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5.dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:clickable="true"
|
||||
android:max="500"
|
||||
tools:progress="100" />
|
||||
|
@ -126,6 +126,7 @@
|
|||
android:layout_alignLeft="@id/butRev"
|
||||
android:layout_alignEnd="@id/butRev"
|
||||
android:layout_alignRight="@id/butRev"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:clickable="false"
|
||||
android:gravity="center"
|
||||
android:text="30"
|
||||
|
@ -155,6 +156,7 @@
|
|||
android:layout_alignLeft="@id/butPlaybackSpeed"
|
||||
android:layout_alignEnd="@id/butPlaybackSpeed"
|
||||
android:layout_alignRight="@id/butPlaybackSpeed"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:clickable="false"
|
||||
android:gravity="center"
|
||||
android:text="1.00"
|
||||
|
@ -186,6 +188,7 @@
|
|||
android:layout_alignLeft="@id/butFF"
|
||||
android:layout_alignEnd="@id/butFF"
|
||||
android:layout_alignRight="@id/butFF"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:clickable="false"
|
||||
android:gravity="center"
|
||||
android:text="30"
|
||||
|
@ -217,6 +220,7 @@
|
|||
android:layout_alignLeft="@id/butSkip"
|
||||
android:layout_alignEnd="@id/butSkip"
|
||||
android:layout_alignRight="@id/butSkip"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:clickable="false"
|
||||
android:gravity="center"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
|
@ -58,12 +58,33 @@
|
|||
android:text="@string/All" />
|
||||
</RadioGroup>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/speed_presets"
|
||||
style="@style/Podcini.TextView.ListItemPrimaryTitle" />
|
||||
android:orientation="horizontal">
|
||||
|
||||
<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
|
||||
android:id="@+id/selected_speeds_grid"
|
||||
|
|
|
@ -27,6 +27,9 @@
|
|||
<string name="years_statistics_label">Years</string>
|
||||
<string name="notification_pref_fragment">Notifications</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_year" translatable="false">Podcini Echo %d</string>
|
||||
|
||||
|
|
|
@ -203,3 +203,9 @@
|
|||
## 4.5.1
|
||||
|
||||
* 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