diff --git a/README.md b/README.md
index 04461830..34af3ee2 100644
--- a/README.md
+++ b/README.md
@@ -82,6 +82,9 @@ Even so, the database remains backward compatible, and AntennaPod's db can be ea
* New share notes menu option on various episode views
* Feed info view offers a link for direct search of feeds related to author
+* New episode home view with two display modes: webpage or reader
+* Text-to-Speech is enabled in reader's mode
+* RSS feeds with no playable media can be subscribed and read/listened
### Online feed
diff --git a/app/build.gradle b/app/build.gradle
index 37b7b5c8..4e10a202 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -149,8 +149,8 @@ android {
// Version code schema (not used):
// "1.2.3-beta4" -> 1020304
// "1.2.3" -> 1020395
- versionCode 3020130
- versionName "4.8.0"
+ versionCode 3020131
+ versionName "4.9.0"
def commit = ""
try {
@@ -227,10 +227,11 @@ dependencies {
implementation "androidx.fragment:fragment-ktx:1.6.2"
implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation "androidx.media:media:1.7.0"
- implementation "androidx.media3:media3-exoplayer:1.2.1"
- implementation "androidx.media3:media3-ui:1.2.1"
- implementation "androidx.media3:media3-datasource-okhttp:1.2.1"
- implementation "androidx.media3:media3-common:1.2.1"
+ implementation "androidx.media3:media3-exoplayer:1.3.1"
+ implementation "androidx.media3:media3-ui:1.3.1"
+ implementation "androidx.media3:media3-datasource-okhttp:1.3.1"
+ implementation "androidx.media3:media3-common:1.3.1"
+ implementation "androidx.media3:media3-session:1.3.1"
implementation "androidx.palette:palette-ktx:1.0.0"
implementation "androidx.preference:preference-ktx:1.2.1"
implementation "androidx.recyclerview:recyclerview:1.3.2"
@@ -269,12 +270,15 @@ dependencies {
implementation 'com.github.xabaras:RecyclerViewSwipeDecorator:1.3'
implementation "com.annimon:stream:1.2.2"
+ implementation "net.dankito.readability4j:readability4j:1.0.8"
+
// Non-free dependencies:
playImplementation 'com.google.android.play:core-ktx:1.8.1'
compileOnly "com.google.android.wearable:wearable:2.9.0"
// this one can not be updated?
androidTestImplementation 'com.nanohttpd:nanohttpd:2.1.1'
+
androidTestImplementation "androidx.test.espresso:espresso-core:3.5.1"
androidTestImplementation "androidx.test.espresso:espresso-contrib:3.5.1"
androidTestImplementation "androidx.test.espresso:espresso-intents:3.5.1"
diff --git a/app/src/free/java/ac/mdiq/podcini/playback/cast/CastPsmp.kt b/app/src/free/java/ac/mdiq/podcini/playback/cast/CastPsmp.kt
index bd6bc55b..9a151060 100644
--- a/app/src/free/java/ac/mdiq/podcini/playback/cast/CastPsmp.kt
+++ b/app/src/free/java/ac/mdiq/podcini/playback/cast/CastPsmp.kt
@@ -9,9 +9,7 @@ import ac.mdiq.podcini.playback.base.PlaybackServiceMediaPlayer.PSMPCallback
*/
object CastPsmp {
@JvmStatic
- fun getInstanceIfConnected(context: Context,
- callback: PSMPCallback
- ): PlaybackServiceMediaPlayer? {
+ fun getInstanceIfConnected(context: Context, callback: PSMPCallback): PlaybackServiceMediaPlayer? {
return null
}
}
diff --git a/app/src/free/java/ac/mdiq/podcini/service/playback/WearMediaSession.kt b/app/src/free/java/ac/mdiq/podcini/service/playback/WearMediaSession.kt
index 4db068f7..aaab7897 100644
--- a/app/src/free/java/ac/mdiq/podcini/service/playback/WearMediaSession.kt
+++ b/app/src/free/java/ac/mdiq/podcini/service/playback/WearMediaSession.kt
@@ -1,7 +1,7 @@
package ac.mdiq.podcini.service.playback
-import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat
+import androidx.media3.session.MediaSession
internal object WearMediaSession {
/**
@@ -11,7 +11,7 @@ internal object WearMediaSession {
// no-op
}
- fun mediaSessionSetExtraForWear(mediaSession: MediaSessionCompat?) {
+ fun mediaSessionSetExtraForWear(mediaSession: MediaSession?) {
// no-op
}
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 39b4ccdc..11da927d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -57,6 +57,7 @@
tools:ignore="ExportedService">
+
diff --git a/app/src/main/java/ac/mdiq/podcini/playback/base/PlaybackServiceMediaPlayer.kt b/app/src/main/java/ac/mdiq/podcini/playback/base/PlaybackServiceMediaPlayer.kt
index eb623982..2199a1d2 100644
--- a/app/src/main/java/ac/mdiq/podcini/playback/base/PlaybackServiceMediaPlayer.kt
+++ b/app/src/main/java/ac/mdiq/podcini/playback/base/PlaybackServiceMediaPlayer.kt
@@ -20,8 +20,8 @@ import kotlin.concurrent.Volatile
* Abstract class that allows for different implementations of the PlaybackServiceMediaPlayer for local
* and remote (cast devices) playback.
*/
-abstract class PlaybackServiceMediaPlayer protected constructor(protected val context: Context,
- protected val callback: PSMPCallback) {
+abstract class PlaybackServiceMediaPlayer protected constructor(protected val context: Context, protected val callback: PSMPCallback) {
+
@Volatile
private var oldPlayerStatus: PlayerStatus? = null
@@ -46,6 +46,7 @@ abstract class PlaybackServiceMediaPlayer protected constructor(protected val co
}
abstract fun isStartWhenPrepared(): Boolean
+
abstract fun setStartWhenPrepared(startWhenPrepared: Boolean)
abstract fun getPlayable(): Playable?
@@ -68,6 +69,8 @@ abstract class PlaybackServiceMediaPlayer protected constructor(protected val co
abstract fun getSelectedAudioTrack(): Int
+ abstract fun createMediaPlayer()
+
/**
* Starts or prepares playback of the specified Playable object. If another Playable object is already being played, the currently playing
* episode will be stopped and replaced with the new Playable object. If the Playable object is already being played, the method will
@@ -98,11 +101,7 @@ abstract class PlaybackServiceMediaPlayer protected constructor(protected val co
* for playback immediately (see 'prepareImmediately' parameter for more details)
* @param prepareImmediately Set to true if the method should also prepare the episode for playback.
*/
- abstract fun playMediaObject(playable: Playable,
- stream: Boolean,
- startWhenPrepared: Boolean,
- prepareImmediately: Boolean
- )
+ abstract fun playMediaObject(playable: Playable, stream: Boolean, startWhenPrepared: Boolean, prepareImmediately: Boolean)
/**
* Resumes playback if the PSMP object is in PREPARED or PAUSED state. If the PSMP object is in an invalid state.
@@ -279,9 +278,7 @@ abstract class PlaybackServiceMediaPlayer protected constructor(protected val co
*
* @return a Future, just for the purpose of tracking its execution.
*/
- protected abstract fun endPlayback(hasEnded: Boolean, wasSkipped: Boolean,
- shouldContinue: Boolean, toStoppedState: Boolean
- )
+ protected abstract fun endPlayback(hasEnded: Boolean, wasSkipped: Boolean, shouldContinue: Boolean, toStoppedState: Boolean)
/**
* @return `true` if the WifiLock feature should be used, `false` otherwise.
@@ -327,9 +324,7 @@ abstract class PlaybackServiceMediaPlayer protected constructor(protected val co
* Will be ignored if given the value of [Playable.INVALID_TIME].
*/
@Synchronized
- protected fun setPlayerStatus(newStatus: PlayerStatus,
- newMedia: Playable?, position: Int
- ) {
+ protected fun setPlayerStatus(newStatus: PlayerStatus, newMedia: Playable?, position: Int) {
Log.d(TAG, this.javaClass.simpleName + ": Setting player status to " + newStatus)
this.oldPlayerStatus = playerStatus
diff --git a/app/src/main/java/ac/mdiq/podcini/playback/service/ExoPlayerWrapper.kt b/app/src/main/java/ac/mdiq/podcini/playback/service/ExoPlayerWrapper.kt
index a40a81c5..d84be978 100644
--- a/app/src/main/java/ac/mdiq/podcini/playback/service/ExoPlayerWrapper.kt
+++ b/app/src/main/java/ac/mdiq/podcini/playback/service/ExoPlayerWrapper.kt
@@ -44,9 +44,6 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
private val bufferUpdateInterval = 5L
private val bufferingUpdateDisposable: Disposable
- private lateinit var exoPlayer: ExoPlayer
- private lateinit var trackSelector: DefaultTrackSelector
-
private var loudnessEnhancer: LoudnessEnhancer? = null
private var mediaSource: MediaSource? = null
private var audioSeekCompleteListener: Runnable? = null
@@ -58,36 +55,18 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
init {
createPlayer()
- playbackParameters = exoPlayer.playbackParameters
+ playbackParameters = exoPlayer!!.playbackParameters
bufferingUpdateDisposable = Observable.interval(bufferUpdateInterval, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
- bufferingUpdateListener?.accept(exoPlayer.bufferedPercentage)
+ bufferingUpdateListener?.accept(exoPlayer!!.bufferedPercentage)
}
}
private fun createPlayer() {
- val loadControl = DefaultLoadControl.Builder()
- loadControl.setBufferDurationsMs(30000, 120000,
- DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
- DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS)
- loadControl.setBackBuffer(UserPreferences.rewindSecs * 1000 + 500, true)
- trackSelector = DefaultTrackSelector(context)
- val audioOffloadPreferences = AudioOffloadPreferences.Builder()
- .setAudioOffloadMode(AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_ENABLED) // Add additional options as needed
- .setIsGaplessSupportRequired(true)
- .setIsSpeedChangeSupportRequired(true)
- .build()
- exoPlayer = ExoPlayer.Builder(context, DefaultRenderersFactory(context))
- .setTrackSelector(trackSelector)
- .setLoadControl(loadControl.build())
- .build()
- exoPlayer.setSeekParameters(SeekParameters.EXACT)
- exoPlayer.trackSelectionParameters = exoPlayer.trackSelectionParameters
- .buildUpon()
- .setAudioOffloadPreferences(audioOffloadPreferences)
- .build()
- exoPlayer.addListener(object : Player.Listener {
+ if (exoPlayer == null) createStaticPlayer(context)
+
+ exoPlayer?.addListener(object : Player.Listener {
override fun onPlaybackStateChanged(playbackState: @Player.State Int) {
when {
audioCompletionListener != null && playbackState == Player.STATE_ENDED -> {
@@ -119,10 +98,7 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
}
}
- override fun onPositionDiscontinuity(oldPosition: PositionInfo,
- newPosition: PositionInfo,
- reason: @DiscontinuityReason Int
- ) {
+ override fun onPositionDiscontinuity(oldPosition: PositionInfo, newPosition: PositionInfo, reason: @DiscontinuityReason Int) {
if (reason == Player.DISCONTINUITY_REASON_SEEK) {
audioSeekCompleteListener?.run()
}
@@ -133,40 +109,37 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
}
})
- initLoudnessEnhancer(exoPlayer.audioSessionId)
+ initLoudnessEnhancer(exoPlayer!!.audioSessionId)
}
val currentPosition: Int
- get() = exoPlayer.currentPosition.toInt()
+ get() = exoPlayer!!.currentPosition.toInt()
val currentSpeedMultiplier: Float
get() = playbackParameters.speed
val duration: Int
get() {
- if (exoPlayer.duration == C.TIME_UNSET) {
- return Playable.INVALID_TIME
- }
- return exoPlayer.duration.toInt()
+ if (exoPlayer?.duration == C.TIME_UNSET) return Playable.INVALID_TIME
+ return exoPlayer!!.duration.toInt()
}
val isPlaying: Boolean
- get() = exoPlayer.playWhenReady
+ get() = exoPlayer!!.playWhenReady
fun pause() {
- exoPlayer.pause()
+ exoPlayer?.pause()
}
@Throws(IllegalStateException::class)
fun prepare() {
if (mediaSource == null) return
- exoPlayer.setMediaSource(mediaSource!!, false)
- exoPlayer.prepare()
+ exoPlayer?.setMediaSource(mediaSource!!, false)
+ exoPlayer?.prepare()
}
fun release() {
bufferingUpdateDisposable.dispose()
- exoPlayer.release()
audioSeekCompleteListener = null
audioCompletionListener = null
@@ -175,23 +148,22 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
}
fun reset() {
- exoPlayer.release()
createPlayer()
}
@Throws(IllegalStateException::class)
fun seekTo(i: Int) {
- exoPlayer.seekTo(i.toLong())
+ exoPlayer?.seekTo(i.toLong())
audioSeekCompleteListener?.run()
}
fun setAudioStreamType(i: Int) {
- val a = exoPlayer.audioAttributes
+ val a = exoPlayer!!.audioAttributes
val b = AudioAttributes.Builder()
b.setContentType(i)
b.setFlags(a.flags)
b.setUsage(a.usage)
- exoPlayer.setAudioAttributes(b.build(), false)
+ exoPlayer?.setAudioAttributes(b.build(), false)
}
@Throws(IllegalArgumentException::class, IllegalStateException::class)
@@ -201,9 +173,8 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
// Call.Factory callFactory = PodciniHttpClient.getHttpClient(); // Assuming it returns OkHttpClient
// OkHttpDataSource.Factory httpDataSourceFactory = new OkHttpDataSource.Factory(callFactory)
// .setUserAgent(ClientConfig.USER_AGENT);
- val httpDataSourceFactory =
- OkHttpDataSource.Factory(PodciniHttpClient.getHttpClient() as okhttp3.Call.Factory)
- .setUserAgent(ClientConfig.USER_AGENT)
+ val httpDataSourceFactory = OkHttpDataSource.Factory(PodciniHttpClient.getHttpClient() as okhttp3.Call.Factory)
+ .setUserAgent(ClientConfig.USER_AGENT)
if (!user.isNullOrEmpty() && !password.isNullOrEmpty()) {
val requestProperties = HashMap()
@@ -225,35 +196,35 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
}
fun setDisplay(sh: SurfaceHolder?) {
- exoPlayer.setVideoSurfaceHolder(sh)
+ exoPlayer?.setVideoSurfaceHolder(sh)
}
fun setPlaybackParams(speed: Float, skipSilence: Boolean) {
Log.d(TAG, "setPlaybackParams speed=$speed pitch=${playbackParameters.pitch} skipSilence=$skipSilence")
playbackParameters = PlaybackParameters(speed, playbackParameters.pitch)
- exoPlayer.skipSilenceEnabled = skipSilence
- exoPlayer.playbackParameters = playbackParameters
+ exoPlayer!!.skipSilenceEnabled = skipSilence
+ exoPlayer!!.playbackParameters = playbackParameters
}
fun setVolume(v: Float, v1: Float) {
if (v > 1) {
- exoPlayer.volume = 1f
+ exoPlayer!!.volume = 1f
loudnessEnhancer?.setEnabled(true)
loudnessEnhancer?.setTargetGain((1000 * (v - 1)).toInt())
} else {
- exoPlayer.volume = v
+ exoPlayer!!.volume = v
loudnessEnhancer?.setEnabled(false)
}
}
fun start() {
- exoPlayer.play()
+ exoPlayer?.play()
// Can't set params when paused - so always set it on start in case they changed
- exoPlayer.playbackParameters = playbackParameters
+ exoPlayer!!.playbackParameters = playbackParameters
}
fun stop() {
- exoPlayer.stop()
+ exoPlayer?.stop()
}
val audioTracks: List
@@ -269,8 +240,7 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
private val formats: List
get() {
val formats: MutableList = arrayListOf()
- val trackInfo = trackSelector.currentMappedTrackInfo
- ?: return emptyList()
+ val trackInfo = trackSelector!!.currentMappedTrackInfo ?: return emptyList()
val trackGroups = trackInfo.getTrackGroups(audioRendererIndex)
for (i in 0 until trackGroups.length) {
formats.add(trackGroups[i].getFormat(0))
@@ -279,28 +249,25 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
}
fun setAudioTrack(track: Int) {
- val trackInfo = trackSelector.currentMappedTrackInfo ?: return
+ val trackInfo = trackSelector!!.currentMappedTrackInfo ?: return
val trackGroups = trackInfo.getTrackGroups(audioRendererIndex)
val override = SelectionOverride(track, 0)
val rendererIndex = audioRendererIndex
- val params = trackSelector.buildUponParameters()
- .setSelectionOverride(rendererIndex, trackGroups, override)
- trackSelector.setParameters(params)
+ val params = trackSelector!!.buildUponParameters().setSelectionOverride(rendererIndex, trackGroups, override)
+ trackSelector!!.setParameters(params)
}
private val audioRendererIndex: Int
get() {
- for (i in 0 until exoPlayer.rendererCount) {
- if (exoPlayer.getRendererType(i) == C.TRACK_TYPE_AUDIO) {
- return i
- }
+ for (i in 0 until exoPlayer!!.rendererCount) {
+ if (exoPlayer?.getRendererType(i) == C.TRACK_TYPE_AUDIO) return i
}
return -1
}
val selectedAudioTrack: Int
get() {
- val trackSelections = exoPlayer.currentTrackSelections
+ val trackSelections = exoPlayer!!.currentTrackSelections
val availableFormats = formats
for (i in 0 until trackSelections.length) {
val track = trackSelections[i] as ExoTrackSelection? ?: continue
@@ -325,12 +292,12 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
val videoWidth: Int
get() {
- return exoPlayer.videoFormat?.width ?: 0
+ return exoPlayer?.videoFormat?.width ?: 0
}
val videoHeight: Int
get() {
- return exoPlayer.videoFormat?.height ?: 0
+ return exoPlayer?.videoFormat?.height ?: 0
}
fun setOnBufferingUpdateListener(bufferingUpdateListener: Consumer?) {
@@ -356,5 +323,33 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
const val BUFFERING_ENDED: Int = -2
private const val TAG = "ExoPlayerWrapper"
const val ERROR_CODE_OFFSET: Int = 1000
+
+ private var trackSelector: DefaultTrackSelector? = null
+ var exoPlayer: ExoPlayer? = null
+
+ fun createStaticPlayer(context: Context) {
+ val loadControl = DefaultLoadControl.Builder()
+ loadControl.setBufferDurationsMs(30000, 120000, DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
+ DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS)
+ loadControl.setBackBuffer(UserPreferences.rewindSecs * 1000 + 500, true)
+ trackSelector = DefaultTrackSelector(context)
+ val audioOffloadPreferences = AudioOffloadPreferences.Builder()
+ .setAudioOffloadMode(AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_ENABLED) // Add additional options as needed
+ .setIsGaplessSupportRequired(true)
+ .setIsSpeedChangeSupportRequired(true)
+ .build()
+ Log.d(TAG, "createPlayer creating exoPlayer_")
+
+ exoPlayer = ExoPlayer.Builder(context, DefaultRenderersFactory(context))
+ .setTrackSelector(trackSelector!!)
+ .setLoadControl(loadControl.build())
+ .build()
+
+ exoPlayer?.setSeekParameters(SeekParameters.EXACT)
+ exoPlayer!!.trackSelectionParameters = exoPlayer!!.trackSelectionParameters
+ .buildUpon()
+ .setAudioOffloadPreferences(audioOffloadPreferences)
+ .build()
+ }
}
}
diff --git a/app/src/main/java/ac/mdiq/podcini/playback/service/LocalPSMP.kt b/app/src/main/java/ac/mdiq/podcini/playback/service/LocalPSMP.kt
index 0cc46044..6fd907ed 100644
--- a/app/src/main/java/ac/mdiq/podcini/playback/service/LocalPSMP.kt
+++ b/app/src/main/java/ac/mdiq/podcini/playback/service/LocalPSMP.kt
@@ -98,11 +98,7 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
* for playback immediately (see 'prepareImmediately' parameter for more details)
* @param prepareImmediately Set to true if the method should also prepare the episode for playback.
*/
- override fun playMediaObject(playable: Playable,
- stream: Boolean,
- startWhenPrepared: Boolean,
- prepareImmediately: Boolean
- ) {
+ override fun playMediaObject(playable: Playable, stream: Boolean, startWhenPrepared: Boolean, prepareImmediately: Boolean) {
Log.d(TAG, "playMediaObject(...)")
try {
playMediaObject(playable, false, stream, startWhenPrepared, prepareImmediately)
@@ -121,12 +117,7 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
*
* @see .playMediaObject
*/
- private fun playMediaObject(playable: Playable,
- forceReset: Boolean,
- stream: Boolean,
- startWhenPrepared: Boolean,
- prepareImmediately: Boolean
- ) {
+ private fun playMediaObject(playable: Playable, forceReset: Boolean, stream: Boolean, startWhenPrepared: Boolean, prepareImmediately: Boolean) {
if (media != null) {
if (!forceReset && media!!.getIdentifier() == playable.getIdentifier() && playerStatus == PlayerStatus.PLAYING) {
// episode is already playing -> ignore method call
@@ -161,7 +152,6 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
try {
callback.ensureMediaInfoLoaded(media!!)
callback.onMediaChanged(false)
-// TODO: speed
setPlaybackParams(PlaybackSpeedUtils.getCurrentPlaybackSpeed(media), UserPreferences.isSkipSilence)
when {
stream -> {
@@ -169,10 +159,7 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
if (streamurl != null) {
if (playable is FeedMedia && playable.item?.feed?.preferences != null) {
val preferences = playable.item!!.feed!!.preferences!!
- mediaPlayer?.setDataSource(
- streamurl,
- preferences.username,
- preferences.password)
+ mediaPlayer?.setDataSource(streamurl, preferences.username, preferences.password)
} else {
mediaPlayer?.setDataSource(streamurl)
}
@@ -221,14 +208,11 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
Log.d(TAG, "Audiofocus successfully requested")
Log.d(TAG, "Resuming/Starting playback")
acquireWifiLockIfNecessary()
-// TODO: speed
setPlaybackParams(PlaybackSpeedUtils.getCurrentPlaybackSpeed(media), UserPreferences.isSkipSilence)
setVolume(1.0f, 1.0f)
if (media != null && playerStatus == PlayerStatus.PREPARED && media!!.getPosition() > 0) {
- val newPosition = RewindAfterPauseUtils.calculatePositionWithRewind(
- media!!.getPosition(),
- media!!.getLastPlayedTime())
+ val newPosition = RewindAfterPauseUtils.calculatePositionWithRewind(media!!.getPosition(), media!!.getLastPlayedTime())
seekTo(newPosition)
}
mediaPlayer?.start()
@@ -502,9 +486,7 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
if (mediaPlayer != null) {
try {
clearMediaPlayerListeners()
- if (mediaPlayer!!.isPlaying) {
- mediaPlayer!!.stop()
- }
+ if (mediaPlayer!!.isPlaying) mediaPlayer!!.stop()
} catch (e: Exception) {
e.printStackTrace()
}
@@ -571,7 +553,7 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
return mediaPlayer?.selectedAudioTrack?:0
}
- private fun createMediaPlayer() {
+ override fun createMediaPlayer() {
mediaPlayer?.release()
if (media == null) {
@@ -650,9 +632,7 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
.build()
}
- override fun endPlayback(hasEnded: Boolean, wasSkipped: Boolean,
- shouldContinue: Boolean, toStoppedState: Boolean
- ) {
+ override fun endPlayback(hasEnded: Boolean, wasSkipped: Boolean, shouldContinue: Boolean, toStoppedState: Boolean) {
releaseWifiLockIfNecessary()
val isPlaying = playerStatus == PlayerStatus.PLAYING
@@ -720,13 +700,9 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
}
private fun setMediaPlayerListeners(mp: ExoPlayerWrapper?) {
- if (mp == null || media == null) {
- return
- }
- mp.setOnCompletionListener(Runnable { endPlayback(hasEnded = true,
- wasSkipped = false,
- shouldContinue = true,
- toStoppedState = true) })
+ if (mp == null || media == null) return
+
+ mp.setOnCompletionListener(Runnable { endPlayback(hasEnded = true, wasSkipped = false, shouldContinue = true, toStoppedState = true) })
mp.setOnSeekCompleteListener(Runnable { this.genericSeekCompleteListener() })
mp.setOnBufferingUpdateListener(Consumer { percent: Int ->
when (percent) {
diff --git a/app/src/main/java/ac/mdiq/podcini/playback/service/PlaybackService.kt b/app/src/main/java/ac/mdiq/podcini/playback/service/PlaybackService.kt
index e3b82c63..fe2de3fe 100644
--- a/app/src/main/java/ac/mdiq/podcini/playback/service/PlaybackService.kt
+++ b/app/src/main/java/ac/mdiq/podcini/playback/service/PlaybackService.kt
@@ -11,11 +11,9 @@ import ac.mdiq.podcini.playback.base.PlayerStatus
import ac.mdiq.podcini.playback.cast.CastPsmp
import ac.mdiq.podcini.playback.cast.CastStateListener
import ac.mdiq.podcini.playback.service.PlaybackServiceTaskManager.PSTMCallback
-import ac.mdiq.podcini.preferences.PlaybackPreferences
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.clearCurrentlyPlayingTemporaryPlaybackSpeed
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.createInstanceFromPreferences
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentEpisodeIsVideo
-import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentPlayerStatus
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentlyPlayingFeedMediaId
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentlyPlayingTemporaryPlaybackSpeed
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.writeMediaPlaying
@@ -26,8 +24,6 @@ import ac.mdiq.podcini.preferences.SleepTimerPreferences.autoEnableFrom
import ac.mdiq.podcini.preferences.SleepTimerPreferences.autoEnableTo
import ac.mdiq.podcini.preferences.SleepTimerPreferences.isInTimeRange
import ac.mdiq.podcini.preferences.SleepTimerPreferences.timerMillis
-import ac.mdiq.podcini.preferences.UserPreferences.allEpisodesSortOrder
-import ac.mdiq.podcini.preferences.UserPreferences.downloadsSortedOrder
import ac.mdiq.podcini.preferences.UserPreferences.fastForwardSecs
import ac.mdiq.podcini.preferences.UserPreferences.getPlaybackSpeed
import ac.mdiq.podcini.preferences.UserPreferences.hardwareForwardButton
@@ -39,7 +35,6 @@ import ac.mdiq.podcini.preferences.UserPreferences.isPersistNotify
import ac.mdiq.podcini.preferences.UserPreferences.isSkipSilence
import ac.mdiq.podcini.preferences.UserPreferences.isUnpauseOnBluetoothReconnect
import ac.mdiq.podcini.preferences.UserPreferences.isUnpauseOnHeadsetReconnect
-import ac.mdiq.podcini.preferences.UserPreferences.playbackSpeedArray
import ac.mdiq.podcini.preferences.UserPreferences.rewindSecs
import ac.mdiq.podcini.preferences.UserPreferences.setPlaybackSpeed
import ac.mdiq.podcini.preferences.UserPreferences.shouldFavoriteKeepEpisode
@@ -52,8 +47,10 @@ import ac.mdiq.podcini.receiver.MediaButtonReceiver
import ac.mdiq.podcini.service.playback.WearMediaSession
import ac.mdiq.podcini.storage.DBReader
import ac.mdiq.podcini.storage.DBWriter
-import ac.mdiq.podcini.storage.FeedSearcher
-import ac.mdiq.podcini.storage.model.feed.*
+import ac.mdiq.podcini.storage.model.feed.Feed
+import ac.mdiq.podcini.storage.model.feed.FeedItem
+import ac.mdiq.podcini.storage.model.feed.FeedMedia
+import ac.mdiq.podcini.storage.model.feed.FeedPreferences
import ac.mdiq.podcini.storage.model.feed.FeedPreferences.AutoDeleteAction
import ac.mdiq.podcini.storage.model.playback.MediaType
import ac.mdiq.podcini.storage.model.playback.Playable
@@ -61,7 +58,6 @@ import ac.mdiq.podcini.ui.activity.appstartintent.MainActivityStarter
import ac.mdiq.podcini.ui.activity.appstartintent.VideoPlayerActivityStarter
import ac.mdiq.podcini.ui.utils.NotificationUtils
import ac.mdiq.podcini.ui.widget.WidgetUpdater.WidgetState
-import ac.mdiq.podcini.util.ChapterUtils.getCurrentChapterIndex
import ac.mdiq.podcini.util.FeedItemUtil.hasAlmostEnded
import ac.mdiq.podcini.util.FeedUtil.shouldAutoDeleteItemsOnThatFeed
import ac.mdiq.podcini.util.IntentUtils.sendLocalBroadcast
@@ -76,15 +72,16 @@ import android.Manifest
import android.annotation.SuppressLint
import android.app.NotificationManager
import android.app.PendingIntent
-import android.app.UiModeManager
import android.bluetooth.BluetoothA2dp
import android.content.*
import android.content.pm.PackageManager
-import android.content.res.Configuration
import android.media.AudioManager
import android.net.Uri
-import android.os.*
+import android.os.Binder
+import android.os.Build
import android.os.Build.VERSION_CODES
+import android.os.IBinder
+import android.os.Vibrator
import android.service.quicksettings.TileService
import android.support.v4.media.MediaBrowserCompat
import android.support.v4.media.MediaDescriptionCompat
@@ -96,7 +93,6 @@ import android.util.Log
import android.util.Pair
import android.view.KeyEvent
import android.view.SurfaceHolder
-import android.view.ViewConfiguration
import android.webkit.URLUtil
import android.widget.Toast
import androidx.annotation.DrawableRes
@@ -104,10 +100,13 @@ import androidx.annotation.StringRes
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
-import androidx.media.MediaBrowserServiceCompat
import androidx.media3.common.util.UnstableApi
-import io.reactivex.*
+import androidx.media3.exoplayer.ExoPlayer
+import androidx.media3.session.MediaLibraryService
+import androidx.media3.session.MediaSession
import io.reactivex.Observable
+import io.reactivex.Single
+import io.reactivex.SingleEmitter
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
@@ -123,7 +122,7 @@ import kotlin.math.max
* Controls the MediaPlayer that plays a FeedMedia-file
*/
@UnstableApi
-class PlaybackService : MediaBrowserServiceCompat() {
+class PlaybackService : MediaLibraryService() {
private var mediaPlayer: PlaybackServiceMediaPlayer? = null
private var positionEventTimer: Disposable? = null
@@ -133,8 +132,8 @@ class PlaybackService : MediaBrowserServiceCompat() {
private lateinit var castStateListener: CastStateListener
private var autoSkippedFeedMediaId: String? = null
- private var clickCount = 0
- private val clickHandler = Handler(Looper.getMainLooper())
+// private var clickCount = 0
+// private val clickHandler = Handler(Looper.getMainLooper())
private var isSpeedForward = false
private var normalSpeed = 1.0f
@@ -145,7 +144,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
/**
* Used for Lollipop notifications, Android Wear, and Android Auto.
*/
- private var mediaSession: MediaSessionCompat? = null
+ private var mediaSession: MediaSession? = null
private val mBinder: IBinder = LocalBinder()
@@ -191,36 +190,14 @@ class PlaybackService : MediaBrowserServiceCompat() {
}
fun recreateMediaSessionIfNeeded() {
- if (mediaSession != null) {
- // Media session was not destroyed, so we can re-use it.
- if (!mediaSession!!.isActive) {
- mediaSession!!.isActive = true
- }
- return
- }
- val eventReceiver = ComponentName(applicationContext, MediaButtonReceiver::class.java)
- val mediaButtonIntent = Intent(Intent.ACTION_MEDIA_BUTTON)
- mediaButtonIntent.setComponent(eventReceiver)
- val buttonReceiverIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent,
- PendingIntent.FLAG_UPDATE_CURRENT or (if (Build.VERSION.SDK_INT >= 31) PendingIntent.FLAG_MUTABLE else 0))
+ if (mediaSession != null) return
- mediaSession = MediaSessionCompat(applicationContext, TAG, eventReceiver, buttonReceiverIntent)
- sessionToken = mediaSession!!.sessionToken
-
- try {
- mediaSession!!.setCallback(sessionCallback)
- mediaSession!!.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS)
- } catch (npe: NullPointerException) {
- // on some devices (Huawei) setting active can cause a NullPointerException
- // even with correct use of the api.
- // See http://stackoverflow.com/questions/31556679/android-huawei-mediassessioncompat
- // and https://plus.google.com/+IanLake/posts/YgdTkKFxz7d
- Log.e(TAG, "NullPointerException while setting up MediaSession")
- npe.printStackTrace()
- }
+ if (ExoPlayerWrapper.exoPlayer == null) ExoPlayerWrapper.createStaticPlayer(applicationContext)
+ mediaSession = MediaSession.Builder(applicationContext, ExoPlayerWrapper.exoPlayer!!)
+ .setCallback(sessionCallback)
+ .build()
recreateMediaPlayer()
- mediaSession!!.isActive = true
}
fun recreateMediaPlayer() {
@@ -234,7 +211,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
}
mediaPlayer = CastPsmp.getInstanceIfConnected(this, mediaPlayerCallback)
if (mediaPlayer == null) {
- mediaPlayer = LocalPSMP(this, mediaPlayerCallback) // Cast not supported or not connected
+ mediaPlayer = LocalPSMP(applicationContext, mediaPlayerCallback) // Cast not supported or not connected
}
if (media != null) {
mediaPlayer!!.playMediaObject(media, !media.localFileAvailable(), wasPlaying, true)
@@ -271,8 +248,13 @@ class PlaybackService : MediaBrowserServiceCompat() {
castStateListener.destroy()
cancelPositionObserver()
- mediaSession?.release()
- mediaSession = null
+ mediaSession?.run {
+ player.release()
+ release()
+ mediaSession = null
+ }
+ ExoPlayerWrapper.exoPlayer?.release()
+ ExoPlayerWrapper.exoPlayer = null
mediaPlayer?.shutdown()
unregisterReceiver(autoStateUpdated)
@@ -284,18 +266,8 @@ class PlaybackService : MediaBrowserServiceCompat() {
EventBus.getDefault().unregister(this)
}
- override fun onGetRoot(clientPackageName: String, clientUid: Int, rootHints: Bundle?): BrowserRoot {
- Log.d(TAG, "OnGetRoot: clientPackageName=" + clientPackageName +
- "; clientUid=" + clientUid + " ; rootHints=" + rootHints)
- if (rootHints != null && rootHints.getBoolean(BrowserRoot.EXTRA_RECENT)) {
- val extras = Bundle()
- extras.putBoolean(BrowserRoot.EXTRA_RECENT, true)
- Log.d(TAG, "OnGetRoot: Returning BrowserRoot " + R.string.current_playing_episode)
- return BrowserRoot(resources.getString(R.string.current_playing_episode), extras)
- }
-
- // Name visible in Android Auto
- return BrowserRoot(resources.getString(R.string.app_name), null)
+ override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession? {
+ return null
}
private fun loadQueueForMediaSession() {
@@ -311,139 +283,141 @@ class PlaybackService : MediaBrowserServiceCompat() {
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
- .subscribe({ queueItems: List? -> mediaSession?.setQueue(queueItems) },
+ .subscribe({ queueItems: List? ->
+// mediaSession?.setQueue(queueItems)
+ },
{ obj: Throwable -> obj.printStackTrace() })
}
- private fun createBrowsableMediaItem(
- @StringRes title: Int, @DrawableRes icon: Int, numEpisodes: Int
- ): MediaBrowserCompat.MediaItem {
- val uri = Uri.Builder()
- .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
- .authority(resources.getResourcePackageName(icon))
- .appendPath(resources.getResourceTypeName(icon))
- .appendPath(resources.getResourceEntryName(icon))
- .build()
+// private fun createBrowsableMediaItem(
+// @StringRes title: Int, @DrawableRes icon: Int, numEpisodes: Int
+// ): MediaBrowserCompat.MediaItem {
+// val uri = Uri.Builder()
+// .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+// .authority(resources.getResourcePackageName(icon))
+// .appendPath(resources.getResourceTypeName(icon))
+// .appendPath(resources.getResourceEntryName(icon))
+// .build()
+//
+// val description = MediaDescriptionCompat.Builder()
+// .setIconUri(uri)
+// .setMediaId(resources.getString(title))
+// .setTitle(resources.getString(title))
+// .setSubtitle(resources.getQuantityString(R.plurals.num_episodes, numEpisodes, numEpisodes))
+// .build()
+// return MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
+// }
- val description = MediaDescriptionCompat.Builder()
- .setIconUri(uri)
- .setMediaId(resources.getString(title))
- .setTitle(resources.getString(title))
- .setSubtitle(resources.getQuantityString(R.plurals.num_episodes, numEpisodes, numEpisodes))
- .build()
- return MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
- }
+// private fun createBrowsableMediaItemForFeed(feed: Feed): MediaBrowserCompat.MediaItem {
+// val builder = MediaDescriptionCompat.Builder()
+// .setMediaId("FeedId:" + feed.id)
+// .setTitle(feed.title)
+// .setDescription(feed.description)
+// .setSubtitle(feed.getCustomTitle())
+// if (feed.imageUrl != null) {
+// builder.setIconUri(Uri.parse(feed.imageUrl))
+// }
+// if (feed.link != null) {
+// builder.setMediaUri(Uri.parse(feed.link))
+// }
+// val description = builder.build()
+// return MediaBrowserCompat.MediaItem(description,
+// MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
+// }
- private fun createBrowsableMediaItemForFeed(feed: Feed): MediaBrowserCompat.MediaItem {
- val builder = MediaDescriptionCompat.Builder()
- .setMediaId("FeedId:" + feed.id)
- .setTitle(feed.title)
- .setDescription(feed.description)
- .setSubtitle(feed.getCustomTitle())
- if (feed.imageUrl != null) {
- builder.setIconUri(Uri.parse(feed.imageUrl))
- }
- if (feed.link != null) {
- builder.setMediaUri(Uri.parse(feed.link))
- }
- val description = builder.build()
- return MediaBrowserCompat.MediaItem(description,
- MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
- }
+// override fun onLoadChildren(parentId: String,
+// result: Result>
+// ) {
+// Log.d(TAG, "OnLoadChildren: parentMediaId=$parentId")
+// result.detach()
+//
+// Completable.create { emitter: CompletableEmitter ->
+// result.sendResult(loadChildrenSynchronous(parentId))
+// emitter.onComplete()
+// }
+// .subscribeOn(Schedulers.io())
+// .observeOn(AndroidSchedulers.mainThread())
+// .subscribe(
+// {}, { e: Throwable ->
+// e.printStackTrace()
+// result.sendResult(null)
+// })
+// }
- override fun onLoadChildren(parentId: String,
- result: Result>
- ) {
- Log.d(TAG, "OnLoadChildren: parentMediaId=$parentId")
- result.detach()
+// private fun loadChildrenSynchronous(parentId: String): List? {
+// val mediaItems: MutableList = ArrayList()
+// if (parentId == resources.getString(R.string.app_name)) {
+// val currentlyPlaying = currentPlayerStatus.toLong()
+// if (currentlyPlaying == PlaybackPreferences.PLAYER_STATUS_PLAYING.toLong()
+// || currentlyPlaying == PlaybackPreferences.PLAYER_STATUS_PAUSED.toLong()) {
+// mediaItems.add(createBrowsableMediaItem(R.string.current_playing_episode, R.drawable.ic_play_48dp, 1))
+// }
+// mediaItems.add(createBrowsableMediaItem(R.string.queue_label, R.drawable.ic_playlist_play_black,
+// DBReader.getTotalEpisodeCount(FeedItemFilter(FeedItemFilter.QUEUED))))
+// mediaItems.add(createBrowsableMediaItem(R.string.downloads_label, R.drawable.ic_download_black,
+// DBReader.getTotalEpisodeCount(FeedItemFilter(FeedItemFilter.DOWNLOADED))))
+// mediaItems.add(createBrowsableMediaItem(R.string.episodes_label, R.drawable.ic_feed_black,
+// DBReader.getTotalEpisodeCount(FeedItemFilter(FeedItemFilter.UNPLAYED))))
+// val feeds = DBReader.getFeedList()
+// for (feed in feeds) {
+// mediaItems.add(createBrowsableMediaItemForFeed(feed))
+// }
+// return mediaItems
+// }
+//
+// val feedItems: List
+// when {
+// parentId == resources.getString(R.string.queue_label) -> {
+// feedItems = DBReader.getQueue()
+// }
+// parentId == resources.getString(R.string.downloads_label) -> {
+// feedItems = DBReader.getEpisodes(0, MAX_ANDROID_AUTO_EPISODES_PER_FEED,
+// FeedItemFilter(FeedItemFilter.DOWNLOADED), downloadsSortedOrder)
+// }
+// parentId == resources.getString(R.string.episodes_label) -> {
+// feedItems = DBReader.getEpisodes(0, MAX_ANDROID_AUTO_EPISODES_PER_FEED,
+// FeedItemFilter(FeedItemFilter.UNPLAYED), allEpisodesSortOrder)
+// }
+// parentId.startsWith("FeedId:") -> {
+// val feedId = parentId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[1].toLong()
+// val feed = DBReader.getFeed(feedId)
+// feedItems = if (feed != null) DBReader.getFeedItemList(feed, FeedItemFilter.unfiltered(), feed.sortOrder) else listOf()
+// }
+// parentId == getString(R.string.current_playing_episode) -> {
+// val playable = createInstanceFromPreferences(this)
+// if (playable is FeedMedia) {
+// feedItems = listOf(playable.item)
+// } else {
+// return null
+// }
+// }
+// else -> {
+// Log.e(TAG, "Parent ID not found: $parentId")
+// return null
+// }
+// }
+// var count = 0
+// for (feedItem in feedItems) {
+// if (feedItem?.media != null) {
+// mediaItems.add(feedItem.media!!.mediaItem)
+// if (++count >= MAX_ANDROID_AUTO_EPISODES_PER_FEED) {
+// break
+// }
+// }
+// }
+// return mediaItems
+// }
- Completable.create { emitter: CompletableEmitter ->
- result.sendResult(loadChildrenSynchronous(parentId))
- emitter.onComplete()
- }
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(
- {}, { e: Throwable ->
- e.printStackTrace()
- result.sendResult(null)
- })
- }
-
- private fun loadChildrenSynchronous(parentId: String): List? {
- val mediaItems: MutableList = ArrayList()
- if (parentId == resources.getString(R.string.app_name)) {
- val currentlyPlaying = currentPlayerStatus.toLong()
- if (currentlyPlaying == PlaybackPreferences.PLAYER_STATUS_PLAYING.toLong()
- || currentlyPlaying == PlaybackPreferences.PLAYER_STATUS_PAUSED.toLong()) {
- mediaItems.add(createBrowsableMediaItem(R.string.current_playing_episode, R.drawable.ic_play_48dp, 1))
- }
- mediaItems.add(createBrowsableMediaItem(R.string.queue_label, R.drawable.ic_playlist_play_black,
- DBReader.getTotalEpisodeCount(FeedItemFilter(FeedItemFilter.QUEUED))))
- mediaItems.add(createBrowsableMediaItem(R.string.downloads_label, R.drawable.ic_download_black,
- DBReader.getTotalEpisodeCount(FeedItemFilter(FeedItemFilter.DOWNLOADED))))
- mediaItems.add(createBrowsableMediaItem(R.string.episodes_label, R.drawable.ic_feed_black,
- DBReader.getTotalEpisodeCount(FeedItemFilter(FeedItemFilter.UNPLAYED))))
- val feeds = DBReader.getFeedList()
- for (feed in feeds) {
- mediaItems.add(createBrowsableMediaItemForFeed(feed))
- }
- return mediaItems
- }
-
- val feedItems: List
- when {
- parentId == resources.getString(R.string.queue_label) -> {
- feedItems = DBReader.getQueue()
- }
- parentId == resources.getString(R.string.downloads_label) -> {
- feedItems = DBReader.getEpisodes(0, MAX_ANDROID_AUTO_EPISODES_PER_FEED,
- FeedItemFilter(FeedItemFilter.DOWNLOADED), downloadsSortedOrder)
- }
- parentId == resources.getString(R.string.episodes_label) -> {
- feedItems = DBReader.getEpisodes(0, MAX_ANDROID_AUTO_EPISODES_PER_FEED,
- FeedItemFilter(FeedItemFilter.UNPLAYED), allEpisodesSortOrder)
- }
- parentId.startsWith("FeedId:") -> {
- val feedId = parentId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[1].toLong()
- val feed = DBReader.getFeed(feedId)
- feedItems = if (feed != null) DBReader.getFeedItemList(feed, FeedItemFilter.unfiltered(), feed.sortOrder) else listOf()
- }
- parentId == getString(R.string.current_playing_episode) -> {
- val playable = createInstanceFromPreferences(this)
- if (playable is FeedMedia) {
- feedItems = listOf(playable.item)
- } else {
- return null
- }
- }
- else -> {
- Log.e(TAG, "Parent ID not found: $parentId")
- return null
- }
- }
- var count = 0
- for (feedItem in feedItems) {
- if (feedItem?.media != null) {
- mediaItems.add(feedItem.media!!.mediaItem)
- if (++count >= MAX_ANDROID_AUTO_EPISODES_PER_FEED) {
- break
- }
- }
- }
- return mediaItems
- }
-
- override fun onBind(intent: Intent): IBinder? {
+ override fun onBind(intent: Intent?): IBinder? {
Log.d(TAG, "Received onBind event")
- return if (intent.action != null && TextUtils.equals(intent.action, SERVICE_INTERFACE)) {
+ return if (intent?.action != null && TextUtils.equals(intent.action, SERVICE_INTERFACE)) {
super.onBind(intent)
} else {
mBinder
}
}
- override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
Log.d(TAG, "OnStartCommand called")
@@ -451,10 +425,10 @@ class PlaybackService : MediaBrowserServiceCompat() {
val notificationManager = NotificationManagerCompat.from(this)
notificationManager.cancel(R.id.notification_streaming_confirmation)
- val keycode = intent.getIntExtra(MediaButtonReceiver.EXTRA_KEYCODE, -1)
- val customAction = intent.getStringExtra(MediaButtonReceiver.EXTRA_CUSTOM_ACTION)
- val hardwareButton = intent.getBooleanExtra(MediaButtonReceiver.EXTRA_HARDWAREBUTTON, false)
- val playable = intent.getParcelableExtra(PlaybackServiceInterface.EXTRA_PLAYABLE)
+ val keycode = intent?.getIntExtra(MediaButtonReceiver.EXTRA_KEYCODE, -1) ?: -1
+ val customAction = intent?.getStringExtra(MediaButtonReceiver.EXTRA_CUSTOM_ACTION)
+ val hardwareButton = intent?.getBooleanExtra(MediaButtonReceiver.EXTRA_HARDWAREBUTTON, false) ?: false
+ val playable = intent?.getParcelableExtra(PlaybackServiceInterface.EXTRA_PLAYABLE)
if (keycode == -1 && playable == null && customAction == null) {
Log.e(TAG, "PlaybackService was started with no arguments")
stateManager.stopService()
@@ -483,10 +457,8 @@ class PlaybackService : MediaBrowserServiceCompat() {
}
playable != null -> {
stateManager.validStartCommandWasReceived()
- val allowStreamThisTime = intent.getBooleanExtra(
- PlaybackServiceInterface.EXTRA_ALLOW_STREAM_THIS_TIME, false)
- val allowStreamAlways = intent.getBooleanExtra(
- PlaybackServiceInterface.EXTRA_ALLOW_STREAM_ALWAYS, false)
+ val allowStreamThisTime = intent.getBooleanExtra(PlaybackServiceInterface.EXTRA_ALLOW_STREAM_THIS_TIME, false)
+ val allowStreamAlways = intent.getBooleanExtra(PlaybackServiceInterface.EXTRA_ALLOW_STREAM_ALWAYS, false)
sendNotificationBroadcast(PlaybackServiceInterface.NOTIFICATION_TYPE_RELOAD, 0)
if (allowStreamAlways) {
isAllowMobileStreaming = true
@@ -510,7 +482,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
return START_NOT_STICKY
}
else -> {
- mediaSession?.controller?.transportControls?.sendCustomAction(customAction, null)
+// mediaSession?.controller?.transportControls?.sendCustomAction(customAction, null)
}
}
}
@@ -541,8 +513,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
@SuppressLint("LaunchActivityFromNotification")
private fun displayStreamingNotAllowedNotification(originalIntent: Intent) {
if (EventBus.getDefault().hasSubscriberForEvent(MessageEvent::class.java)) {
- EventBus.getDefault().post(MessageEvent(
- getString(R.string.confirm_mobile_streaming_notification_message)))
+ EventBus.getDefault().post(MessageEvent(getString(R.string.confirm_mobile_streaming_notification_message)))
return
}
@@ -550,24 +521,22 @@ class PlaybackService : MediaBrowserServiceCompat() {
intentAllowThisTime.setAction(PlaybackServiceInterface.EXTRA_ALLOW_STREAM_THIS_TIME)
intentAllowThisTime.putExtra(PlaybackServiceInterface.EXTRA_ALLOW_STREAM_THIS_TIME, true)
val pendingIntentAllowThisTime = if (Build.VERSION.SDK_INT >= VERSION_CODES.O) {
- PendingIntent.getForegroundService(this,
- R.id.pending_intent_allow_stream_this_time, intentAllowThisTime,
+ PendingIntent.getForegroundService(this, R.id.pending_intent_allow_stream_this_time, intentAllowThisTime,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
} else {
- PendingIntent.getService(this,
- R.id.pending_intent_allow_stream_this_time, intentAllowThisTime, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
+ PendingIntent.getService(this, R.id.pending_intent_allow_stream_this_time, intentAllowThisTime,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
}
val intentAlwaysAllow = Intent(intentAllowThisTime)
intentAlwaysAllow.setAction(PlaybackServiceInterface.EXTRA_ALLOW_STREAM_ALWAYS)
intentAlwaysAllow.putExtra(PlaybackServiceInterface.EXTRA_ALLOW_STREAM_ALWAYS, true)
val pendingIntentAlwaysAllow = if (Build.VERSION.SDK_INT >= VERSION_CODES.O) {
- PendingIntent.getForegroundService(this,
- R.id.pending_intent_allow_stream_always, intentAlwaysAllow,
+ PendingIntent.getForegroundService(this, R.id.pending_intent_allow_stream_always, intentAlwaysAllow,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
} else {
- PendingIntent.getService(this,
- R.id.pending_intent_allow_stream_always, intentAlwaysAllow, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
+ PendingIntent.getService(this, R.id.pending_intent_allow_stream_always, intentAlwaysAllow,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
}
val builder = NotificationCompat.Builder(this,
@@ -579,12 +548,8 @@ class PlaybackService : MediaBrowserServiceCompat() {
.bigText(getString(R.string.confirm_mobile_streaming_notification_message)))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntentAllowThisTime)
- .addAction(R.drawable.ic_notification_stream,
- getString(R.string.confirm_mobile_streaming_button_once),
- pendingIntentAllowThisTime)
- .addAction(R.drawable.ic_notification_stream,
- getString(R.string.confirm_mobile_streaming_button_always),
- pendingIntentAlwaysAllow)
+ .addAction(R.drawable.ic_notification_stream, getString(R.string.confirm_mobile_streaming_button_once), pendingIntentAllowThisTime)
+ .addAction(R.drawable.ic_notification_stream, getString(R.string.confirm_mobile_streaming_button_always), pendingIntentAlwaysAllow)
.setAutoCancel(true)
val notificationManager = NotificationManagerCompat.from(this)
if (Build.VERSION.SDK_INT >= 33 && ActivityCompat.checkSelfPermission(this,
@@ -725,8 +690,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
private fun startPlayingFromPreferences() {
Observable.fromCallable {
- createInstanceFromPreferences(
- applicationContext)
+ createInstanceFromPreferences(applicationContext)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@@ -856,8 +820,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
}
}
if (Build.VERSION.SDK_INT >= VERSION_CODES.N) {
- TileService.requestListeningState(applicationContext,
- ComponentName(applicationContext, QuickSettingsTileService::class.java))
+ TileService.requestListeningState(applicationContext, ComponentName(applicationContext, QuickSettingsTileService::class.java))
}
sendLocalBroadcast(applicationContext, ACTION_PLAYER_STATUS_CHANGED)
@@ -878,9 +841,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
updateNotificationAndMediaSession(this@PlaybackService.playable)
}
- override fun onPostPlayback(media: Playable, ended: Boolean, skipped: Boolean,
- playingNext: Boolean
- ) {
+ override fun onPostPlayback(media: Playable, ended: Boolean, skipped: Boolean, playingNext: Boolean) {
this@PlaybackService.onPostPlayback(media, ended, skipped, playingNext)
}
@@ -1088,12 +1049,10 @@ class PlaybackService : MediaBrowserServiceCompat() {
if (media is FeedMedia) {
if (ended || smartMarkAsPlayed) {
- SynchronizationQueueSink.enqueueEpisodePlayedIfSynchronizationIsActive(
- applicationContext, media, true)
+ SynchronizationQueueSink.enqueueEpisodePlayedIfSynchronizationIsActive(applicationContext, media, true)
media.onPlaybackCompleted(applicationContext)
} else {
- SynchronizationQueueSink.enqueueEpisodePlayedIfSynchronizationIsActive(
- applicationContext, media, false)
+ SynchronizationQueueSink.enqueueEpisodePlayedIfSynchronizationIsActive(applicationContext, media, false)
media.onPlaybackPause(applicationContext)
}
}
@@ -1108,12 +1067,11 @@ class PlaybackService : MediaBrowserServiceCompat() {
val action = item.feed?.preferences?.currentAutoDelete
val shouldAutoDelete = (action == AutoDeleteAction.ALWAYS
|| (action == AutoDeleteAction.GLOBAL && item.feed != null && shouldAutoDeleteItemsOnThatFeed(item.feed!!)))
- if (media is FeedMedia && shouldAutoDelete &&
- (!item.isTagged(FeedItem.TAG_FAVORITE) || !shouldFavoriteKeepEpisode())) {
+ if (media is FeedMedia && shouldAutoDelete && (!item.isTagged(FeedItem.TAG_FAVORITE) || !shouldFavoriteKeepEpisode())) {
DBWriter.deleteFeedMediaOfItem(this@PlaybackService, media.id)
Log.d(TAG, "Episode Deleted")
}
- notifyChildrenChanged(getString(R.string.queue_label))
+// notifyChildrenChanged(getString(R.string.queue_label))
}
}
@@ -1153,13 +1111,13 @@ class PlaybackService : MediaBrowserServiceCompat() {
val skipEndMS = skipEnd * 1000
// Log.d(TAG, "skipEndingIfNecessary: checking " + remainingTime + " " + skipEndMS + " speed " + currentPlaybackSpeed)
if (skipEnd > 0 && skipEndMS < this.duration && (remainingTime - skipEndMS < 0)) {
- Log.d(TAG, "skipEndingIfNecessary: Skipping the remaining " + remainingTime + " " + skipEndMS + " speed " + currentPlaybackSpeed)
+ Log.d(TAG, "skipEndingIfNecessary: Skipping the remaining $remainingTime $skipEndMS speed $currentPlaybackSpeed")
val context = applicationContext
val skipMesg = context.getString(R.string.pref_feed_skip_ending_toast, skipEnd)
val toast = Toast.makeText(context, skipMesg, Toast.LENGTH_LONG)
toast.show()
- this.autoSkippedFeedMediaId = item?.identifyingValue
+ this.autoSkippedFeedMediaId = item.identifyingValue
mediaPlayer?.skip()
}
}
@@ -1199,51 +1157,35 @@ class PlaybackService : MediaBrowserServiceCompat() {
// On Android Auto, custom actions are added in the following order around the play button, if no default
// actions are present: Near left, near right, far left, far right, additional actions panel
- val rewindBuilder = PlaybackStateCompat.CustomAction.Builder(
- CUSTOM_ACTION_REWIND,
- getString(R.string.rewind_label),
- R.drawable.ic_notification_fast_rewind
- )
+ val rewindBuilder = PlaybackStateCompat.CustomAction.Builder(CUSTOM_ACTION_REWIND, getString(R.string.rewind_label), R.drawable.ic_notification_fast_rewind)
WearMediaSession.addWearExtrasToAction(rewindBuilder)
sessionState.addCustomAction(rewindBuilder.build())
- val fastForwardBuilder = PlaybackStateCompat.CustomAction.Builder(
- CUSTOM_ACTION_FAST_FORWARD,
- getString(R.string.fast_forward_label),
- R.drawable.ic_notification_fast_forward
- )
+ val fastForwardBuilder = PlaybackStateCompat.CustomAction.Builder(CUSTOM_ACTION_FAST_FORWARD, getString(R.string.fast_forward_label), R.drawable.ic_notification_fast_forward)
WearMediaSession.addWearExtrasToAction(fastForwardBuilder)
sessionState.addCustomAction(fastForwardBuilder.build())
if (showPlaybackSpeedOnFullNotification()) {
- sessionState.addCustomAction(
- PlaybackStateCompat.CustomAction.Builder(
- CUSTOM_ACTION_CHANGE_PLAYBACK_SPEED,
- getString(R.string.playback_speed),
- R.drawable.ic_notification_playback_speed
- ).build()
- )
+ sessionState.addCustomAction(PlaybackStateCompat.CustomAction.Builder(CUSTOM_ACTION_CHANGE_PLAYBACK_SPEED,
+ getString(R.string.playback_speed), R.drawable.ic_notification_playback_speed).build())
}
if (showNextChapterOnFullNotification()) {
if (!playable?.getChapters().isNullOrEmpty()) {
- sessionState.addCustomAction(
- PlaybackStateCompat.CustomAction.Builder(
- CUSTOM_ACTION_NEXT_CHAPTER,
- getString(R.string.next_chapter), R.drawable.ic_notification_next_chapter)
- .build())
+ sessionState.addCustomAction(PlaybackStateCompat.CustomAction.Builder(CUSTOM_ACTION_NEXT_CHAPTER,
+ getString(R.string.next_chapter), R.drawable.ic_notification_next_chapter).build())
}
}
if (showSkipOnFullNotification()) {
- sessionState.addCustomAction(
- PlaybackStateCompat.CustomAction.Builder(CUSTOM_ACTION_SKIP_TO_NEXT, getString(R.string.skip_episode_label), R.drawable.ic_notification_skip).build()
+ sessionState.addCustomAction(PlaybackStateCompat.CustomAction.Builder(CUSTOM_ACTION_SKIP_TO_NEXT,
+ getString(R.string.skip_episode_label), R.drawable.ic_notification_skip).build()
)
}
if (mediaSession != null) {
WearMediaSession.mediaSessionSetExtraForWear(mediaSession!!)
- mediaSession!!.setPlaybackState(sessionState.build())
+// mediaSession!!.setPlaybackState(sessionState.build())
}
}
@@ -1253,9 +1195,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
}
private fun updateMediaSessionMetadata(p: Playable?) {
- if (p == null || mediaSession == null) {
- return
- }
+ if (p == null || mediaSession == null) return
val builder = MediaMetadataCompat.Builder()
builder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, p.getFeedTitle())
@@ -1294,13 +1234,13 @@ class PlaybackService : MediaBrowserServiceCompat() {
mediaSession!!.setSessionActivity(PendingIntent.getActivity(this, R.id.pending_intent_player_activity,
getPlayerActivityIntent(this), PendingIntent.FLAG_UPDATE_CURRENT
or (if (Build.VERSION.SDK_INT >= 31) PendingIntent.FLAG_MUTABLE else 0)))
- try {
- mediaSession!!.setMetadata(builder.build())
- } catch (e: OutOfMemoryError) {
- Log.e(TAG, "Setting media session metadata", e)
- builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, null)
- mediaSession!!.setMetadata(builder.build())
- }
+// try {
+// mediaSession!!.setMetadata(builder.build())
+// } catch (e: OutOfMemoryError) {
+// Log.e(TAG, "Setting media session metadata", e)
+// builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, null)
+// mediaSession!!.setMetadata(builder.build())
+// }
}
}
@@ -1327,7 +1267,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
val playerStatus = mediaPlayer!!.playerStatus
notificationBuilder.setPlayable(playable)
- if (mediaSession != null) notificationBuilder.setMediaSessionToken(mediaSession!!.sessionToken)
+ if (mediaSession != null) notificationBuilder.setMediaSessionToken(mediaSession!!.getSessionCompatToken())
notificationBuilder.playerStatus = playerStatus
notificationBuilder.updatePosition(currentPosition, currentPlaybackSpeed)
@@ -1646,8 +1586,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
feedPreferences.feedPlaybackSpeed = speed
Log.d(TAG, "setSpeed ${feed.title} $speed")
DBWriter.setFeedPreferences(feedPreferences)
- EventBus.getDefault().post(
- SpeedPresetChangedEvent(feedPreferences.feedPlaybackSpeed, feed.id))
+ EventBus.getDefault().post(SpeedPresetChangedEvent(feedPreferences.feedPlaybackSpeed, feed.id))
}
}
}
@@ -1776,188 +1715,13 @@ class PlaybackService : MediaBrowserServiceCompat() {
if (playable is FeedMedia) {
val itemId = playable.item?.id ?: return
DBWriter.addQueueItem(this, false, true, itemId)
- notifyChildrenChanged(getString(R.string.queue_label))
+// notifyChildrenChanged(getString(R.string.queue_label))
}
}
- private val sessionCallback: MediaSessionCompat.Callback = object : MediaSessionCompat.Callback() {
+ private val sessionCallback: MediaSession.Callback = object : MediaSession.Callback {
private val TAG = "MediaSessionCompat"
-
- override fun onPlay() {
- Log.d(TAG, "onPlay()")
- val status: PlayerStatus = this@PlaybackService.status
- when (status) {
- PlayerStatus.PAUSED, PlayerStatus.PREPARED -> {
- resume()
- }
- PlayerStatus.INITIALIZED -> {
- this@PlaybackService.isStartWhenPrepared = true
- prepare()
- }
- else -> {}
- }
- }
-
- override fun onPlayFromMediaId(mediaId: String, extras: Bundle) {
- Log.d(TAG, "onPlayFromMediaId: mediaId: $mediaId extras: $extras")
- val p = DBReader.getFeedMedia(mediaId.toLong())
- if (p != null) {
- startPlaying(p, false)
- }
- }
-
- override fun onPlayFromSearch(query: String, extras: Bundle) {
- Log.d(TAG, "onPlayFromSearch query=$query extras=$extras")
-
- if (query == "") {
- Log.d(TAG, "onPlayFromSearch called with empty query, resuming from the last position")
- startPlayingFromPreferences()
- return
- }
-
- val results = FeedSearcher.searchFeedItems(query, 0)
- if (results.isNotEmpty() && results[0].media != null) {
- val media = results[0].media
- startPlaying(media, false)
- return
- }
- onPlay()
- }
-
- override fun onPause() {
- Log.d(TAG, "onPause()")
- if (this@PlaybackService.status == PlayerStatus.PLAYING) {
- pause(!isPersistNotify, false)
- }
- }
-
- override fun onStop() {
- Log.d(TAG, "onStop()")
- mediaPlayer?.stopPlayback(true)
- }
-
- override fun onSkipToPrevious() {
- Log.d(TAG, "onSkipToPrevious()")
- seekDelta(-rewindSecs * 1000)
- }
-
- override fun onRewind() {
- Log.d(TAG, "onRewind()")
- seekDelta(-rewindSecs * 1000)
- }
-
- fun onNextChapter() {
- val chapters = mediaPlayer?.getPlayable()?.getChapters() ?: listOf()
- if (chapters.isEmpty()) {
- // No chapters, just fallback to next episode
- mediaPlayer?.skip()
- return
- }
-
- val nextChapter = getCurrentChapterIndex(mediaPlayer?.getPlayable(), (mediaPlayer?.getPosition()?:0)) + 1
-
- if (chapters.size < nextChapter + 1) {
- // We are on the last chapter, just fallback to the next episode
- mediaPlayer?.skip()
- return
- }
-
- mediaPlayer?.seekTo(chapters[nextChapter].start.toInt())
- }
-
- override fun onFastForward() {
- Log.d(TAG, "onFastForward()")
-// speedForward(2.5f)
- seekDelta(fastForwardSecs * 1000)
- }
-
- override fun onSkipToNext() {
- Log.d(TAG, "onSkipToNext()")
- val uiModeManager = applicationContext.getSystemService(UI_MODE_SERVICE) as UiModeManager
- if (hardwareForwardButton == KeyEvent.KEYCODE_MEDIA_NEXT
- || uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_CAR) {
- mediaPlayer?.skip()
- } else {
- seekDelta(fastForwardSecs * 1000)
- }
- }
-
-
- override fun onSeekTo(pos: Long) {
- Log.d(TAG, "onSeekTo()")
- seekTo(pos.toInt())
- }
-
- override fun onSetPlaybackSpeed(speed: Float) {
- Log.d(TAG, "onSetPlaybackSpeed()")
- setSpeed(speed)
- }
-
- override fun onMediaButtonEvent(mediaButton: Intent): Boolean {
- Log.d(TAG, "onMediaButtonEvent($mediaButton)")
- val keyEvent = mediaButton.getParcelableExtra(Intent.EXTRA_KEY_EVENT)
- if (keyEvent != null && keyEvent.action == KeyEvent.ACTION_DOWN && keyEvent.repeatCount == 0) {
- val keyCode = keyEvent.keyCode
- if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
- clickCount++
- clickHandler.removeCallbacksAndMessages(null)
- clickHandler.postDelayed({
- when (clickCount) {
- 1 -> {
- handleKeycode(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, false)
- }
- 2 -> {
- onFastForward()
- }
- 3 -> {
- onRewind()
- }
- }
- clickCount = 0
- }, ViewConfiguration.getDoubleTapTimeout().toLong())
- return true
- } else {
- return handleKeycode(keyCode, false)
- }
- }
- return false
- }
-
- override fun onCustomAction(action: String, extra: Bundle) {
- Log.d(TAG, "onCustomAction($action)")
- when (action) {
- CUSTOM_ACTION_FAST_FORWARD -> {
- onFastForward()
- }
- CUSTOM_ACTION_REWIND -> {
- onRewind()
- }
- CUSTOM_ACTION_SKIP_TO_NEXT -> {
- mediaPlayer?.skip()
- }
- CUSTOM_ACTION_NEXT_CHAPTER -> {
- onNextChapter()
- }
- CUSTOM_ACTION_CHANGE_PLAYBACK_SPEED -> {
- val selectedSpeeds = playbackSpeedArray
-
- // If the list has zero or one element, there's nothing we can do to change the playback speed.
- if (selectedSpeeds.size > 1) {
- val speedPosition = selectedSpeeds.indexOf(mediaPlayer?.getPlaybackSpeed()?:0f)
-
- val newSpeed = if (speedPosition == selectedSpeeds.size - 1) {
- // This is the last element. Wrap instead of going over the size of the list.
- selectedSpeeds[0]
- } else {
- // If speedPosition is still -1 (the user isn't using a preset), use the first preset in the
- // list.
- selectedSpeeds[speedPosition + 1]
- }
- onSetPlaybackSpeed(newSpeed)
- }
- }
- }
- }
+// TODO: not used now with media3
}
companion object {
@@ -1976,8 +1740,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
private const val CUSTOM_ACTION_SKIP_TO_NEXT = "action.ac.mdiq.podcini.service.skipToNext"
private const val CUSTOM_ACTION_FAST_FORWARD = "action.ac.mdiq.podcini.service.fastForward"
private const val CUSTOM_ACTION_REWIND = "action.ac.mdiq.podcini.service.rewind"
- private const val CUSTOM_ACTION_CHANGE_PLAYBACK_SPEED =
- "action.ac.mdiq.podcini.service.changePlaybackSpeed"
+ private const val CUSTOM_ACTION_CHANGE_PLAYBACK_SPEED = "action.ac.mdiq.podcini.service.changePlaybackSpeed"
const val CUSTOM_ACTION_NEXT_CHAPTER: String = "action.ac.mdiq.podcini.service.next_chapter"
/**
@@ -2030,15 +1793,16 @@ class PlaybackService : MediaBrowserServiceCompat() {
/**
* Same as [.getPlayerActivityIntent], but here the type of activity
- * depends on the FeedMedia that is provided as an argument.
+ * depends on the medaitype that is provided as an argument.
*/
@JvmStatic
- fun getPlayerActivityIntent(context: Context, media: Playable): Intent {
- return if (media.getMediaType() == MediaType.VIDEO && !isCasting) {
+ fun getPlayerActivityIntent(context: Context, mediaType: MediaType?): Intent {
+ return if (mediaType == MediaType.VIDEO && !isCasting) {
VideoPlayerActivityStarter(context).intent
} else {
MainActivityStarter(context).withOpenPlayer().getIntent()
}
}
+
}
}
diff --git a/app/src/main/java/ac/mdiq/podcini/playback/service/PlaybackServiceNotificationBuilder.kt b/app/src/main/java/ac/mdiq/podcini/playback/service/PlaybackServiceNotificationBuilder.kt
index 2d2b6e46..ffaab349 100644
--- a/app/src/main/java/ac/mdiq/podcini/playback/service/PlaybackServiceNotificationBuilder.kt
+++ b/app/src/main/java/ac/mdiq/podcini/playback/service/PlaybackServiceNotificationBuilder.kt
@@ -130,43 +130,33 @@ class PlaybackServiceNotificationBuilder(private val context: Context) {
private val playerActivityPendingIntent: PendingIntent
get() = PendingIntent.getActivity(context, R.id.pending_intent_player_activity,
- PlaybackService.getPlayerActivityIntent(context), PendingIntent.FLAG_UPDATE_CURRENT
- or PendingIntent.FLAG_IMMUTABLE)
+ PlaybackService.getPlayerActivityIntent(context), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
private fun addActions(notification: NotificationCompat.Builder, mediaSessionToken: MediaSessionCompat.Token?,
- playerStatus: PlayerStatus?
- ) {
+ playerStatus: PlayerStatus?) {
val compactActionList = ArrayList()
var numActions = 0 // we start and 0 and then increment by 1 for each call to addAction
- val rewindButtonPendingIntent = getPendingIntentForMediaAction(
- KeyEvent.KEYCODE_MEDIA_REWIND, numActions)
- notification.addAction(R.drawable.ic_notification_fast_rewind, context.getString(R.string.rewind_label),
- rewindButtonPendingIntent)
+ val rewindButtonPendingIntent = getPendingIntentForMediaAction(KeyEvent.KEYCODE_MEDIA_REWIND, numActions)
+ notification.addAction(R.drawable.ic_notification_fast_rewind, context.getString(R.string.rewind_label), rewindButtonPendingIntent)
compactActionList.add(numActions)
numActions++
if (playerStatus == PlayerStatus.PLAYING) {
- val pauseButtonPendingIntent = getPendingIntentForMediaAction(
- KeyEvent.KEYCODE_MEDIA_PAUSE, numActions)
- notification.addAction(R.drawable.ic_notification_pause, //pause action
- context.getString(R.string.pause_label),
- pauseButtonPendingIntent)
+ val pauseButtonPendingIntent = getPendingIntentForMediaAction(KeyEvent.KEYCODE_MEDIA_PAUSE, numActions)
+ //pause action
+ notification.addAction(R.drawable.ic_notification_pause, context.getString(R.string.pause_label), pauseButtonPendingIntent)
} else {
- val playButtonPendingIntent = getPendingIntentForMediaAction(
- KeyEvent.KEYCODE_MEDIA_PLAY, numActions)
- notification.addAction(R.drawable.ic_notification_play, //play action
- context.getString(R.string.play_label),
- playButtonPendingIntent)
+ val playButtonPendingIntent = getPendingIntentForMediaAction(KeyEvent.KEYCODE_MEDIA_PLAY, numActions)
+ //play action
+ notification.addAction(R.drawable.ic_notification_play, context.getString(R.string.play_label), playButtonPendingIntent)
}
compactActionList.add(numActions++)
// ff follows play, then we have skip (if it's present)
- val ffButtonPendingIntent = getPendingIntentForMediaAction(
- KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, numActions)
- notification.addAction(R.drawable.ic_notification_fast_forward, context.getString(R.string.fast_forward_label),
- ffButtonPendingIntent)
+ val ffButtonPendingIntent = getPendingIntentForMediaAction(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, numActions)
+ notification.addAction(R.drawable.ic_notification_fast_forward, context.getString(R.string.fast_forward_label), ffButtonPendingIntent)
compactActionList.add(numActions)
numActions++
@@ -177,15 +167,12 @@ class PlaybackServiceNotificationBuilder(private val context: Context) {
}
if (UserPreferences.showSkipOnFullNotification()) {
- val skipButtonPendingIntent = getPendingIntentForMediaAction(
- KeyEvent.KEYCODE_MEDIA_NEXT, numActions)
- notification.addAction(R.drawable.ic_notification_skip, context.getString(R.string.skip_episode_label),
- skipButtonPendingIntent)
+ val skipButtonPendingIntent = getPendingIntentForMediaAction(KeyEvent.KEYCODE_MEDIA_NEXT, numActions)
+ notification.addAction(R.drawable.ic_notification_skip, context.getString(R.string.skip_episode_label), skipButtonPendingIntent)
numActions++
}
- val stopButtonPendingIntent = getPendingIntentForMediaAction(
- KeyEvent.KEYCODE_MEDIA_STOP, numActions)
+ val stopButtonPendingIntent = getPendingIntentForMediaAction(KeyEvent.KEYCODE_MEDIA_STOP, numActions)
notification.setStyle(androidx.media.app.NotificationCompat.MediaStyle()
.setMediaSession(mediaSessionToken)
.setShowActionsInCompactView(*ArrayUtils.toPrimitive(compactActionList.toTypedArray()))
@@ -199,11 +186,9 @@ class PlaybackServiceNotificationBuilder(private val context: Context) {
intent.putExtra(MediaButtonReceiver.EXTRA_KEYCODE, keycodeValue)
return if (Build.VERSION.SDK_INT >= 26) {
- PendingIntent.getForegroundService(context, requestCode, intent,
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
+ PendingIntent.getForegroundService(context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
} else {
- PendingIntent.getService(context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT
- or PendingIntent.FLAG_IMMUTABLE)
+ PendingIntent.getService(context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
}
}
@@ -213,11 +198,9 @@ class PlaybackServiceNotificationBuilder(private val context: Context) {
intent.putExtra(MediaButtonReceiver.EXTRA_CUSTOM_ACTION, action)
return if (Build.VERSION.SDK_INT >= 26) {
- PendingIntent.getForegroundService(context, requestCode, intent,
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
+ PendingIntent.getForegroundService(context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
} else {
- PendingIntent.getService(context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT
- or PendingIntent.FLAG_IMMUTABLE)
+ PendingIntent.getService(context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
}
}
@@ -230,8 +213,7 @@ class PlaybackServiceNotificationBuilder(private val context: Context) {
private var defaultIcon: Bitmap? = null
private fun getBitmap(vectorDrawable: VectorDrawable): Bitmap {
- val bitmap = Bitmap.createBitmap(vectorDrawable.intrinsicWidth,
- vectorDrawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
+ val bitmap = Bitmap.createBitmap(vectorDrawable.intrinsicWidth, vectorDrawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
vectorDrawable.setBounds(0, 0, canvas.width, canvas.height)
vectorDrawable.draw(canvas)
diff --git a/app/src/main/java/ac/mdiq/podcini/playback/service/PlaybackVolumeUpdater.kt b/app/src/main/java/ac/mdiq/podcini/playback/service/PlaybackVolumeUpdater.kt
index f4d0aac0..03c826d3 100644
--- a/app/src/main/java/ac/mdiq/podcini/playback/service/PlaybackVolumeUpdater.kt
+++ b/app/src/main/java/ac/mdiq/podcini/playback/service/PlaybackVolumeUpdater.kt
@@ -7,8 +7,7 @@ import ac.mdiq.podcini.playback.base.PlayerStatus
internal class PlaybackVolumeUpdater {
fun updateVolumeIfNecessary(mediaPlayer: PlaybackServiceMediaPlayer, feedId: Long,
- volumeAdaptionSetting: VolumeAdaptionSetting
- ) {
+ volumeAdaptionSetting: VolumeAdaptionSetting) {
val playable = mediaPlayer.getPlayable()
if (playable is FeedMedia) {
@@ -17,8 +16,7 @@ internal class PlaybackVolumeUpdater {
}
private fun updateFeedMediaVolumeIfNecessary(mediaPlayer: PlaybackServiceMediaPlayer, feedId: Long,
- volumeAdaptionSetting: VolumeAdaptionSetting, feedMedia: FeedMedia
- ) {
+ volumeAdaptionSetting: VolumeAdaptionSetting, feedMedia: FeedMedia) {
if (feedMedia.item?.feed?.id == feedId) {
val preferences = feedMedia.item!!.feed!!.preferences
if (preferences != null) preferences.volumeAdaptionSetting = volumeAdaptionSetting
diff --git a/app/src/main/java/ac/mdiq/podcini/storage/database/PodDBAdapter.kt b/app/src/main/java/ac/mdiq/podcini/storage/database/PodDBAdapter.kt
index f598a910..dc1081cf 100644
--- a/app/src/main/java/ac/mdiq/podcini/storage/database/PodDBAdapter.kt
+++ b/app/src/main/java/ac/mdiq/podcini/storage/database/PodDBAdapter.kt
@@ -91,8 +91,7 @@ class PodDBAdapter private constructor() {
feed.id = db.insert(TABLE_NAME_FEEDS, null, values)
} else {
Log.d(this.toString(), "Updating existing Feed in db")
- db.update(TABLE_NAME_FEEDS, values, "$KEY_ID=?",
- arrayOf(feed.id.toString()))
+ db.update(TABLE_NAME_FEEDS, values, "$KEY_ID=?", arrayOf(feed.id.toString()))
}
return feed.id
}
@@ -116,14 +115,12 @@ class PodDBAdapter private constructor() {
values.put(KEY_FEED_SKIP_ENDING, prefs.feedSkipEnding)
values.put(KEY_EPISODE_NOTIFICATION, prefs.showEpisodeNotification)
values.put(KEY_NEW_EPISODES_ACTION, prefs.newEpisodesAction!!.code)
- db.update(TABLE_NAME_FEEDS, values, "$KEY_ID=?", arrayOf(
- prefs.feedID.toString()))
+ db.update(TABLE_NAME_FEEDS, values, "$KEY_ID=?", arrayOf(prefs.feedID.toString()))
}
fun setFeedItemFilter(feedId: Long, filterValues: Set?) {
val valuesList = TextUtils.join(",", filterValues!!)
- Log.d(TAG, String.format(Locale.US,
- "setFeedItemFilter() called with: feedId = [%d], filterValues = [%s]", feedId, valuesList))
+ Log.d(TAG, String.format(Locale.US, "setFeedItemFilter() called with: feedId = [%d], filterValues = [%s]", feedId, valuesList))
val values = ContentValues()
values.put(KEY_HIDE, valuesList)
db.update(TABLE_NAME_FEEDS, values, "$KEY_ID=?", arrayOf(feedId.toString()))
@@ -163,8 +160,7 @@ class PodDBAdapter private constructor() {
if (media.id == 0L) {
media.id = db.insert(TABLE_NAME_FEED_MEDIA, null, values)
} else {
- db.update(TABLE_NAME_FEED_MEDIA, values, "$KEY_ID=?",
- arrayOf(media.id.toString()))
+ db.update(TABLE_NAME_FEED_MEDIA, values, "$KEY_ID=?", arrayOf(media.id.toString()))
}
return media.id
}
@@ -176,8 +172,7 @@ class PodDBAdapter private constructor() {
values.put(KEY_DURATION, media.getDuration())
values.put(KEY_PLAYED_DURATION, media.playedDuration)
values.put(KEY_LAST_PLAYED_TIME, media.getLastPlayedTime())
- db.update(TABLE_NAME_FEED_MEDIA, values, "$KEY_ID=?",
- arrayOf(media.id.toString()))
+ db.update(TABLE_NAME_FEED_MEDIA, values, "$KEY_ID=?", arrayOf(media.id.toString()))
} else {
Log.e(TAG, "setFeedMediaPlaybackInformation: ID of media was 0")
}
@@ -317,8 +312,7 @@ class PodDBAdapter private constructor() {
if (item.id == 0L) {
item.id = db.insert(TABLE_NAME_FEED_ITEMS, null, values)
} else {
- db.update(TABLE_NAME_FEED_ITEMS, values, "$KEY_ID=?",
- arrayOf(item.id.toString()))
+ db.update(TABLE_NAME_FEED_ITEMS, values, "$KEY_ID=?", arrayOf(item.id.toString()))
}
if (item.media != null) {
setMedia(item.media)
@@ -329,9 +323,7 @@ class PodDBAdapter private constructor() {
return item.id
}
- fun setFeedItemRead(played: Int, itemId: Long, mediaId: Long,
- resetMediaPosition: Boolean
- ) {
+ fun setFeedItemRead(played: Int, itemId: Long, mediaId: Long, resetMediaPosition: Boolean) {
try {
db.beginTransactionNonExclusive()
val values = ContentValues()
@@ -387,8 +379,7 @@ class PodDBAdapter private constructor() {
if (chapter.id == 0L) {
chapter.id = db.insert(TABLE_NAME_SIMPLECHAPTERS, null, values)
} else {
- db.update(TABLE_NAME_SIMPLECHAPTERS, values, "$KEY_ID=?",
- arrayOf(chapter.id.toString()))
+ db.update(TABLE_NAME_SIMPLECHAPTERS, values, "$KEY_ID=?", arrayOf(chapter.id.toString()))
}
}
}
@@ -428,8 +419,7 @@ class PodDBAdapter private constructor() {
if (status.id == 0L) {
status.id = db.insert(TABLE_NAME_DOWNLOAD_LOG, null, values)
} else {
- db.update(TABLE_NAME_DOWNLOAD_LOG, values, "$KEY_ID=?",
- arrayOf(status.id.toString()))
+ db.update(TABLE_NAME_DOWNLOAD_LOG, values, "$KEY_ID=?", arrayOf(status.id.toString()))
}
return status.id
}
@@ -470,16 +460,12 @@ class PodDBAdapter private constructor() {
}
fun removeFavoriteItem(item: FeedItem) {
- val deleteClause = String.format("DELETE FROM %s WHERE %s=%s AND %s=%s",
- TABLE_NAME_FAVORITES,
- KEY_FEEDITEM, item.id,
- KEY_FEED, item.feedId)
+ val deleteClause = String.format("DELETE FROM %s WHERE %s=%s AND %s=%s", TABLE_NAME_FAVORITES, KEY_FEEDITEM, item.id, KEY_FEED, item.feedId)
db.execSQL(deleteClause)
}
private fun isItemInFavorites(item: FeedItem): Boolean {
- val query = String.format(Locale.US, "SELECT %s from %s WHERE %s=%d",
- KEY_ID, TABLE_NAME_FAVORITES, KEY_FEEDITEM, item.id)
+ val query = String.format(Locale.US, "SELECT %s from %s WHERE %s=%d", KEY_ID, TABLE_NAME_FAVORITES, KEY_FEEDITEM, item.id)
val c = db.rawQuery(query, null)
val count = c.count
c.close()
@@ -557,8 +543,7 @@ class PodDBAdapter private constructor() {
db.delete(TABLE_NAME_DOWNLOAD_LOG, "$KEY_FEEDFILE=? AND $KEY_FEEDFILETYPE=?",
arrayOf(feed.id.toString(), Feed.FEEDFILETYPE_FEED.toString()))
- db.delete(TABLE_NAME_FEEDS, "$KEY_ID=?",
- arrayOf(feed.id.toString()))
+ db.delete(TABLE_NAME_FEEDS, "$KEY_ID=?", arrayOf(feed.id.toString()))
db.setTransactionSuccessful()
} catch (e: SQLException) {
Log.e(TAG, Log.getStackTraceString(e))
@@ -717,8 +702,7 @@ class PodDBAdapter private constructor() {
val orderByQuery = generateFrom(sortOrder)
val filterQuery = generateFrom(filter!!)
val whereClause = if ("" == filterQuery) "" else " WHERE $filterQuery"
- val query = (SELECT_FEED_ITEMS_AND_MEDIA + whereClause
- + "ORDER BY " + orderByQuery + " LIMIT " + offset + ", " + limit)
+ val query = (SELECT_FEED_ITEMS_AND_MEDIA + whereClause + "ORDER BY " + orderByQuery + " LIMIT " + offset + ", " + limit)
return db.rawQuery(query, null)
}
@@ -771,8 +755,7 @@ class PodDBAdapter private constructor() {
get() = DatabaseUtils.queryNumEntries(db, TABLE_NAME_FEED_MEDIA, "$KEY_PLAYBACK_COMPLETION_DATE> 0")
fun getSingleFeedMediaCursor(id: Long): Cursor {
- val query = ("SELECT " + KEYS_FEED_MEDIA + " FROM " + TABLE_NAME_FEED_MEDIA
- + " WHERE " + KEY_ID + "=" + id)
+ val query = ("SELECT " + KEYS_FEED_MEDIA + " FROM " + TABLE_NAME_FEED_MEDIA + " WHERE " + KEY_ID + "=" + id)
return db.rawQuery(query, null)
}
@@ -918,8 +901,7 @@ class PodDBAdapter private constructor() {
fun getFeedCounters(setting: FeedCounter?, vararg feedIds: Long): Map {
val whereRead = when (setting) {
// FeedCounter.SHOW_NEW -> KEY_READ + "=" + FeedItem.NEW
- FeedCounter.SHOW_UNPLAYED -> ("(" + KEY_READ + "=" + FeedItem.NEW
- + " OR " + KEY_READ + "=" + FeedItem.UNPLAYED + ")")
+ FeedCounter.SHOW_UNPLAYED -> ("(" + KEY_READ + "=" + FeedItem.NEW + " OR " + KEY_READ + "=" + FeedItem.UNPLAYED + ")")
FeedCounter.SHOW_DOWNLOADED -> "$KEY_DOWNLOADED=1"
FeedCounter.SHOW_DOWNLOADED_UNPLAYED -> ("(" + KEY_READ + "=" + FeedItem.NEW
+ " OR " + KEY_READ + "=" + FeedItem.UNPLAYED + ")"
@@ -1044,13 +1026,11 @@ class PodDBAdapter private constructor() {
"1 = 1"
}
- val queryStart = (SELECT_FEED_ITEMS_AND_MEDIA_WITH_DESCRIPTION
- + " WHERE " + queryFeedId + " AND (")
+ val queryStart = (SELECT_FEED_ITEMS_AND_MEDIA_WITH_DESCRIPTION + " WHERE " + queryFeedId + " AND (")
val sb = StringBuilder(queryStart)
for (i in queryWords.indices) {
- sb
- .append("(")
+ sb.append("(")
.append("$KEY_DESCRIPTION LIKE '%").append(queryWords[i])
.append("%' OR ")
.append(KEY_TITLE).append(" LIKE '%").append(queryWords[i])
@@ -1078,8 +1058,7 @@ class PodDBAdapter private constructor() {
val sb = StringBuilder(queryStart)
for (i in queryWords.indices) {
- sb
- .append("(")
+ sb.append("(")
.append(KEY_TITLE).append(" LIKE '%").append(queryWords[i])
.append("%' OR ")
.append(KEY_CUSTOM_TITLE).append(" LIKE '%").append(queryWords[i])
diff --git a/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/PlayActionButton.kt b/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/PlayActionButton.kt
index c4d34318..8940dacb 100644
--- a/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/PlayActionButton.kt
+++ b/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/PlayActionButton.kt
@@ -3,9 +3,9 @@ package ac.mdiq.podcini.ui.actions.actionbutton
import android.content.Context
import androidx.media3.common.util.UnstableApi
import ac.mdiq.podcini.R
-import ac.mdiq.podcini.playback.service.PlaybackService.Companion.getPlayerActivityIntent
import ac.mdiq.podcini.storage.DBTasks
import ac.mdiq.podcini.playback.PlaybackServiceStarter
+import ac.mdiq.podcini.playback.service.PlaybackService.Companion.getPlayerActivityIntent
import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.storage.model.playback.MediaType
import ac.mdiq.podcini.util.event.playback.StartPlayEvent
@@ -33,7 +33,7 @@ class PlayActionButton(item: FeedItem) : ItemActionButton(item) {
.start()
if (media.getMediaType() == MediaType.VIDEO) {
- context.startActivity(getPlayerActivityIntent(context, media))
+ context.startActivity(getPlayerActivityIntent(context, MediaType.VIDEO))
}
}
}
diff --git a/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/PlayLocalActionButton.kt b/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/PlayLocalActionButton.kt
index 696ffad5..f0b7ecbe 100644
--- a/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/PlayLocalActionButton.kt
+++ b/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/PlayLocalActionButton.kt
@@ -23,7 +23,7 @@ class PlayLocalActionButton(item: FeedItem?) : ItemActionButton(item!!) {
.start()
if (media.getMediaType() == MediaType.VIDEO) {
- context.startActivity(getPlayerActivityIntent(context, media))
+ context.startActivity(getPlayerActivityIntent(context, MediaType.VIDEO))
}
}
}
diff --git a/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/StreamActionButton.kt b/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/StreamActionButton.kt
index ddab6132..a7959876 100644
--- a/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/StreamActionButton.kt
+++ b/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/StreamActionButton.kt
@@ -43,7 +43,7 @@ class StreamActionButton(item: FeedItem) : ItemActionButton(item) {
.start()
if (media.getMediaType() == MediaType.VIDEO) {
- context.startActivity(getPlayerActivityIntent(context, media))
+ context.startActivity(getPlayerActivityIntent(context, MediaType.VIDEO))
}
}
}
diff --git a/app/src/main/java/ac/mdiq/podcini/ui/actions/menuhandler/FeedItemMenuHandler.kt b/app/src/main/java/ac/mdiq/podcini/ui/actions/menuhandler/FeedItemMenuHandler.kt
index 0a874ecc..c246fdc0 100644
--- a/app/src/main/java/ac/mdiq/podcini/ui/actions/menuhandler/FeedItemMenuHandler.kt
+++ b/app/src/main/java/ac/mdiq/podcini/ui/actions/menuhandler/FeedItemMenuHandler.kt
@@ -85,9 +85,8 @@ object FeedItemMenuHandler {
* @param visibility The new visibility status of given menu item
*/
private fun setItemVisibility(menu: Menu?, menuId: Int, visibility: Boolean) {
- if (menu == null) {
- return
- }
+ if (menu == null) return
+
val item = menu.findItem(menuId)
item?.setVisible(visibility)
}
@@ -112,9 +111,8 @@ object FeedItemMenuHandler {
*/
@UnstableApi
fun onPrepareMenu(menu: Menu?, selectedItem: FeedItem?, vararg excludeIds: Int): Boolean {
- if (menu == null || selectedItem == null) {
- return false
- }
+ if (menu == null || selectedItem == null) return false
+
val rc = onPrepareMenu(menu, selectedItem)
if (rc && excludeIds.isNotEmpty()) {
for (id in excludeIds) {
diff --git a/app/src/main/java/ac/mdiq/podcini/ui/activity/MainActivity.kt b/app/src/main/java/ac/mdiq/podcini/ui/activity/MainActivity.kt
index b62ad692..bfac9e85 100644
--- a/app/src/main/java/ac/mdiq/podcini/ui/activity/MainActivity.kt
+++ b/app/src/main/java/ac/mdiq/podcini/ui/activity/MainActivity.kt
@@ -274,6 +274,7 @@ class MainActivity : CastEnabledActivity() {
private val bottomSheetCallback: BottomSheetCallback = @UnstableApi object : BottomSheetCallback() {
override fun onStateChanged(view: View, state: Int) {
+ Log.d(TAG, "bottomSheet onStateChanged $state")
when (state) {
BottomSheetBehavior.STATE_COLLAPSED -> {
onSlide(view,0.0f)
@@ -610,8 +611,8 @@ class MainActivity : CastEnabledActivity() {
bottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED)
}
intent.getBooleanExtra(MainActivityStarter.EXTRA_OPEN_PLAYER, false) -> {
- bottomSheet.state = BottomSheetBehavior.STATE_EXPANDED
- bottomSheetCallback.onSlide(dummyView, 1.0f)
+// bottomSheet.state = BottomSheetBehavior.STATE_EXPANDED
+// bottomSheetCallback.onSlide(dummyView, 1.0f)
}
else -> {
handleDeeplink(intent.data)
diff --git a/app/src/main/java/ac/mdiq/podcini/ui/activity/VideoplayerActivity.kt b/app/src/main/java/ac/mdiq/podcini/ui/activity/VideoplayerActivity.kt
index 46358742..473072df 100644
--- a/app/src/main/java/ac/mdiq/podcini/ui/activity/VideoplayerActivity.kt
+++ b/app/src/main/java/ac/mdiq/podcini/ui/activity/VideoplayerActivity.kt
@@ -40,39 +40,45 @@ import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
-
/**
* Activity for playing video files.
*/
@UnstableApi
class VideoplayerActivity : CastEnabledActivity() {
+ enum class VideoMode(val mode: Int) {
+ None(0,),
+ WINDOW_VIEW(1),
+ FULL_SCREEN_VIEW(2),
+ AUDIO_ONLY(3)
+ }
+
private var _binding: VideoplayerActivityBinding? = null
private val binding get() = _binding!!
lateinit var videoEpisodeFragment: VideoEpisodeFragment
- var videoMode = 0
var switchToAudioOnly = false
+
override fun onCreate(savedInstanceState: Bundle?) {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
- videoMode = intent.getIntExtra("fullScreenMode",0)
- if (videoMode == 0) {
- videoMode = videoPlayMode
- if (videoMode == AUDIO_ONLY) {
+ videoMode = (intent.getSerializableExtra(VIDEO_MODE) as? VideoMode) ?: VideoMode.None
+ if (videoMode == VideoMode.None) {
+ videoMode = VideoMode.entries.toTypedArray().getOrElse(videoPlayMode) { VideoMode.WINDOW_VIEW }
+ if (videoMode == VideoMode.AUDIO_ONLY) {
switchToAudioOnly = true
finish()
}
- if (videoMode != FULL_SCREEN_VIEW && videoMode != WINDOW_VIEW) {
+ if (videoMode != VideoMode.FULL_SCREEN_VIEW && videoMode != VideoMode.WINDOW_VIEW) {
Log.i(TAG, "videoMode not selected, use window mode")
- videoMode = WINDOW_VIEW
+ videoMode = VideoMode.WINDOW_VIEW
}
}
when (videoMode) {
- FULL_SCREEN_VIEW -> {
+ VideoMode.FULL_SCREEN_VIEW -> {
window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN)
// has to be called before setting layout content
supportRequestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY)
@@ -81,13 +87,14 @@ class VideoplayerActivity : CastEnabledActivity() {
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
window.setFormat(PixelFormat.TRANSPARENT)
}
- WINDOW_VIEW -> {
+ VideoMode.WINDOW_VIEW -> {
supportRequestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY)
setTheme(R.style.Theme_Podcini_VideoEpisode)
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
window.setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN, WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN)
window.setFormat(PixelFormat.TRANSPARENT)
}
+ else -> {}
}
super.onCreate(savedInstanceState)
@@ -156,7 +163,7 @@ class VideoplayerActivity : CastEnabledActivity() {
fun toggleViews() {
val newIntent = Intent(this, VideoplayerActivity::class.java)
- newIntent.putExtra("fullScreenMode", if (videoMode == FULL_SCREEN_VIEW) WINDOW_VIEW else FULL_SCREEN_VIEW)
+ newIntent.putExtra(VIDEO_MODE, if (videoMode == VideoMode.FULL_SCREEN_VIEW) VideoMode.WINDOW_VIEW else VideoMode.FULL_SCREEN_VIEW)
finish()
startActivity(newIntent)
}
@@ -233,7 +240,7 @@ class VideoplayerActivity : CastEnabledActivity() {
menu.findItem(R.id.playback_speed).setVisible(true)
menu.findItem(R.id.player_show_chapters).setVisible(true)
- if (videoMode == WINDOW_VIEW) {
+ if (videoMode == VideoMode.WINDOW_VIEW) {
menu.findItem(R.id.add_to_favorites_item).setShowAsAction(SHOW_AS_ACTION_NEVER)
menu.findItem(R.id.remove_from_favorites_item).setShowAsAction(SHOW_AS_ACTION_NEVER)
menu.findItem(R.id.set_sleeptimer_item).setShowAsAction(SHOW_AS_ACTION_NEVER)
@@ -316,7 +323,7 @@ class VideoplayerActivity : CastEnabledActivity() {
private fun compatEnterPictureInPicture() {
if (PictureInPictureUtil.supportsPictureInPicture(this) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- if (videoMode == FULL_SCREEN_VIEW) supportActionBar?.hide()
+ if (videoMode == VideoMode.FULL_SCREEN_VIEW) supportActionBar?.hide()
videoEpisodeFragment.hideVideoControls(false)
enterPictureInPictureMode()
}
@@ -381,9 +388,9 @@ class VideoplayerActivity : CastEnabledActivity() {
companion object {
private const val TAG = "VideoplayerActivity"
- const val WINDOW_VIEW = 1
- const val FULL_SCREEN_VIEW = 2
- const val AUDIO_ONLY = 3
+ const val VIDEO_MODE = "Video_Mode"
+
+ var videoMode = VideoMode.None
private fun getWebsiteLinkWithFallback(media: Playable?): String? {
return when {
diff --git a/app/src/main/java/ac/mdiq/podcini/ui/activity/appstartintent/VideoPlayerActivityStarter.kt b/app/src/main/java/ac/mdiq/podcini/ui/activity/appstartintent/VideoPlayerActivityStarter.kt
index 5008c6a7..4399f523 100644
--- a/app/src/main/java/ac/mdiq/podcini/ui/activity/appstartintent/VideoPlayerActivityStarter.kt
+++ b/app/src/main/java/ac/mdiq/podcini/ui/activity/appstartintent/VideoPlayerActivityStarter.kt
@@ -2,21 +2,25 @@ package ac.mdiq.podcini.ui.activity.appstartintent
import ac.mdiq.podcini.R
+import ac.mdiq.podcini.ui.activity.VideoplayerActivity.Companion.VIDEO_MODE
+import ac.mdiq.podcini.ui.activity.VideoplayerActivity.VideoMode
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
-import android.os.Build
+import androidx.annotation.OptIn
+import androidx.media3.common.util.UnstableApi
/**
* Launches the video player activity of the app with specific arguments.
* Does not require a dependency on the actual implementation of the activity.
*/
-class VideoPlayerActivityStarter(private val context: Context) {
+@OptIn(UnstableApi::class) class VideoPlayerActivityStarter(private val context: Context, mode: VideoMode = VideoMode.None) {
val intent: Intent = Intent(INTENT)
init {
intent.setPackage(context.packageName)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
+ if (mode != VideoMode.None) intent.putExtra(VIDEO_MODE, mode)
}
val pendingIntent: PendingIntent
diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt
index 4da2f77d..63d55e78 100644
--- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt
+++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt
@@ -6,36 +6,35 @@ 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.PlaybackController.Companion
-import ac.mdiq.podcini.playback.PlaybackServiceStarter
import ac.mdiq.podcini.playback.base.PlayerStatus
import ac.mdiq.podcini.playback.cast.CastEnabledActivity
-import ac.mdiq.podcini.preferences.UserPreferences
-import ac.mdiq.podcini.receiver.MediaButtonReceiver
import ac.mdiq.podcini.playback.service.PlaybackService
+import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.preferences.UserPreferences.videoPlayMode
+import ac.mdiq.podcini.receiver.MediaButtonReceiver
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.actions.menuhandler.FeedItemMenuHandler
import ac.mdiq.podcini.ui.activity.MainActivity
-import ac.mdiq.podcini.ui.view.PlaybackSpeedIndicatorView
+import ac.mdiq.podcini.ui.activity.VideoplayerActivity.Companion.videoMode
+import ac.mdiq.podcini.ui.activity.VideoplayerActivity.VideoMode
+import ac.mdiq.podcini.ui.activity.appstartintent.VideoPlayerActivityStarter
import ac.mdiq.podcini.ui.dialog.MediaPlayerErrorDialog
import ac.mdiq.podcini.ui.dialog.SkipPreferenceDialog
import ac.mdiq.podcini.ui.dialog.SleepTimerDialog
import ac.mdiq.podcini.ui.dialog.VariableSpeedDialog
-import ac.mdiq.podcini.ui.actions.menuhandler.FeedItemMenuHandler
-import ac.mdiq.podcini.ui.activity.VideoplayerActivity.Companion.AUDIO_ONLY
import ac.mdiq.podcini.ui.view.ChapterSeekBar
import ac.mdiq.podcini.ui.view.PlayButton
+import ac.mdiq.podcini.ui.view.PlaybackSpeedIndicatorView
import ac.mdiq.podcini.util.ChapterUtils
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.playback.*
-import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.os.Build
@@ -114,10 +113,13 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
toolbar = binding.toolbar
toolbar.title = ""
toolbar.setNavigationOnClickListener {
- val bottomSheet = (activity as MainActivity).bottomSheet
- if (bottomSheet.state == BottomSheetBehavior.STATE_EXPANDED)
- bottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED
- else bottomSheet.state = BottomSheetBehavior.STATE_EXPANDED
+// val mtype = controller?.getMedia()?.getMediaType()
+// if (mtype == MediaType.AUDIO || (mtype == MediaType.VIDEO && videoPlayMode == VideoMode.AUDIO_ONLY)) {
+ val bottomSheet = (activity as MainActivity).bottomSheet
+// if (bottomSheet.state == BottomSheetBehavior.STATE_EXPANDED)
+ bottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED
+// else bottomSheet.state = BottomSheetBehavior.STATE_EXPANDED
+// }
}
toolbar.setOnMenuItemClickListener(this)
@@ -400,6 +402,9 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
if (item == null && isFeedMedia) item = (media as FeedMedia).item
FeedItemMenuHandler.onPrepareMenu(toolbar.menu, item)
+ val mediaType = controller?.getMedia()?.getMediaType()
+ toolbar.menu?.findItem(R.id.show_video)?.setVisible(mediaType == MediaType.VIDEO)
+
if (controller != null) {
toolbar.menu.findItem(R.id.set_sleeptimer_item).setVisible(!controller!!.sleepTimerActive())
toolbar.menu.findItem(R.id.disable_sleeptimer_item).setVisible(controller!!.sleepTimerActive())
@@ -419,6 +424,11 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
val itemId = menuItem.itemId
when (itemId) {
+ R.id.show_video -> {
+ controller!!.playPause()
+ VideoPlayerActivityStarter(requireContext(), VideoMode.FULL_SCREEN_VIEW).start()
+ return true
+ }
R.id.disable_sleeptimer_item, R.id.set_sleeptimer_item -> {
SleepTimerDialog().show(childFragmentManager, "SleepTimerDialog")
return true
@@ -523,13 +533,15 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
Log.d(TAG, "internalPlayerFragment was clicked")
val media = controller?.getMedia()
if (media != null) {
- if (media.getMediaType() == MediaType.AUDIO || videoPlayMode == AUDIO_ONLY) {
+ val mediaType = media.getMediaType()
+ if (mediaType == MediaType.AUDIO ||
+ (mediaType == MediaType.VIDEO && (videoPlayMode == VideoMode.AUDIO_ONLY.mode || videoMode == VideoMode.AUDIO_ONLY))) {
controller!!.ensureService()
(activity as MainActivity).bottomSheet.setState(BottomSheetBehavior.STATE_EXPANDED)
} else {
controller?.playPause()
// controller!!.ensureService()
- val intent = PlaybackService.getPlayerActivityIntent(requireContext(), media)
+ val intent = PlaybackService.getPlayerActivityIntent(requireContext(), mediaType)
startActivity(intent)
}
}
@@ -554,7 +566,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
val media = controller!!.getMedia()
if (media?.getMediaType() == MediaType.VIDEO && controller!!.status != PlayerStatus.PLAYING) {
controller!!.playPause()
- requireContext().startActivity(PlaybackService.getPlayerActivityIntent(requireContext(), media))
+ requireContext().startActivity(PlaybackService.getPlayerActivityIntent(requireContext(), media?.getMediaType()))
} else {
controller!!.playPause()
}
@@ -569,8 +581,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
}
}
butRev.setOnLongClickListener {
- SkipPreferenceDialog.showSkipPreference(requireContext(),
- SkipPreferenceDialog.SkipDirection.SKIP_REWIND, txtvRev)
+ SkipPreferenceDialog.showSkipPreference(requireContext(), SkipPreferenceDialog.SkipDirection.SKIP_REWIND, txtvRev)
true
}
butPlay.setOnClickListener {
diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/EpisodeHomeFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/EpisodeHomeFragment.kt
new file mode 100644
index 00000000..02789ee1
--- /dev/null
+++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/EpisodeHomeFragment.kt
@@ -0,0 +1,234 @@
+package ac.mdiq.podcini.ui.fragment
+
+import ac.mdiq.podcini.R
+import ac.mdiq.podcini.databinding.EpisodeHomeFragmentBinding
+import ac.mdiq.podcini.storage.model.feed.FeedItem
+import android.speech.tts.TextToSpeech
+import android.os.Build
+import android.os.Bundle
+import android.text.Html
+import android.util.Log
+import android.view.*
+import androidx.annotation.OptIn
+import androidx.appcompat.widget.Toolbar
+import androidx.core.app.ShareCompat
+import androidx.fragment.app.Fragment
+import androidx.media3.common.util.UnstableApi
+import com.google.android.material.appbar.MaterialToolbar
+import io.reactivex.disposables.Disposable
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+import net.dankito.readability4j.Readability4J
+import java.io.BufferedReader
+import java.io.InputStreamReader
+import java.net.URL
+import java.util.*
+
+
+/**
+ * Displays information about an Episode (FeedItem) and actions.
+ */
+class EpisodeHomeFragment : Fragment(), Toolbar.OnMenuItemClickListener, TextToSpeech.OnInitListener {
+ private var _binding: EpisodeHomeFragmentBinding? = null
+ private val binding get() = _binding!!
+
+ private var item: FeedItem? = null
+
+ private lateinit var tts: TextToSpeech
+ private lateinit var toolbar: MaterialToolbar
+
+ private var disposable: Disposable? = null
+
+ private var readerhtml: String? = null
+ private var textContent: String? = null
+ private var readMode = false
+ private var ttsPlaying = false
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ item = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) requireArguments().getSerializable(ARG_FEEDITEM, FeedItem::class.java)
+ else requireArguments().getSerializable(ARG_FEEDITEM) as? FeedItem
+ tts = TextToSpeech(requireContext(), this)
+ }
+
+ @UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+ super.onCreateView(inflater, container, savedInstanceState)
+
+ _binding = EpisodeHomeFragmentBinding.inflate(inflater, container, false)
+ Log.d(TAG, "fragment onCreateView")
+
+ toolbar = binding.toolbar
+ toolbar.title = ""
+ toolbar.inflateMenu(R.menu.episode_home)
+ toolbar.setNavigationOnClickListener { parentFragmentManager.popBackStack() }
+ toolbar.setOnMenuItemClickListener(this)
+
+ if (item?.link != null) {
+ showContent()
+ }
+ updateAppearance()
+ return binding.root
+ }
+
+ @OptIn(UnstableApi::class) private fun switchMode() {
+ readMode = !readMode
+ showContent()
+ updateAppearance()
+ }
+
+ override fun onInit(status: Int) {
+ if (status == TextToSpeech.SUCCESS) {
+ // TTS initialization successful
+ Log.i(TAG, "TTS init success with Locale: ${item?.feed?.language}")
+ if (item?.feed?.language != null) {
+ val result = tts.setLanguage(Locale(item!!.feed!!.language))
+// val result = tts.setLanguage(Locale.UK)
+ if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
+ Log.w(TAG, "TTS language not supported")
+ // Language not supported
+ // Handle the error or fallback to default behavior
+ }
+ }
+ } else {
+ // TTS initialization failed
+ // Handle the error or fallback to default behavior
+ Log.w(TAG, "TTS init failed")
+ }
+ }
+
+ private fun showContent() {
+ if (readMode) {
+ if (readerhtml == null) {
+ runBlocking {
+ val url = item!!.link!!
+ val htmlSource = fetchHtmlSource(url)
+ val readability4J = Readability4J(item?.link!!, htmlSource)
+ val article = readability4J.parse()
+ textContent = article.textContent
+// Log.d(TAG, "readability4J: ${article.textContent}")
+ readerhtml = article.contentWithDocumentsCharsetOrUtf8
+ }
+ }
+ if (readerhtml != null) binding.webView.loadDataWithBaseURL(item!!.link!!, readerhtml!!, "text/html", "UTF-8", null)
+ } else {
+ if (item?.link != null) binding.webView.loadUrl(item!!.link!!)
+ }
+ }
+
+ private suspend fun fetchHtmlSource(urlString: String): String = withContext(Dispatchers.IO) {
+ val url = URL(urlString)
+ val connection = url.openConnection()
+ val inputStream = connection.getInputStream()
+ val bufferedReader = BufferedReader(InputStreamReader(inputStream))
+
+ val stringBuilder = StringBuilder()
+ var line: String?
+ while (bufferedReader.readLine().also { line = it } != null) {
+ stringBuilder.append(line)
+ }
+
+ bufferedReader.close()
+ inputStream.close()
+
+ stringBuilder.toString()
+ }
+
+ override fun onPrepareOptionsMenu(menu: Menu) {
+ val textSpeech = menu.findItem(R.id.text_speech)
+ textSpeech.isVisible = readMode
+ if (readMode) {
+ if (ttsPlaying) textSpeech.setIcon(R.drawable.ic_pause)
+ else textSpeech.setIcon(R.drawable.ic_play_24dp)
+ }
+ }
+
+ @UnstableApi override fun onMenuItemClick(menuItem: MenuItem): Boolean {
+ when (menuItem.itemId) {
+ R.id.switch_home -> {
+ Log.d(TAG, "switch_home selected")
+ switchMode()
+ return true
+ }
+ R.id.text_speech -> {
+ Log.d(TAG, "text_speech selected: $textContent")
+ if (tts.isSpeaking) tts.stop()
+ if (!ttsPlaying) {
+ ttsPlaying = true
+ if (textContent != null) {
+ val maxTextLength = 4000
+ var startIndex = 0
+ var endIndex = minOf(maxTextLength, textContent!!.length)
+ while (startIndex < textContent!!.length) {
+ val chunk = textContent!!.substring(startIndex, endIndex)
+ tts.speak(chunk, TextToSpeech.QUEUE_ADD, null, null)
+
+ startIndex += maxTextLength
+ endIndex = minOf(endIndex + maxTextLength, textContent!!.length)
+ }
+ }
+ } else ttsPlaying = false
+
+ updateAppearance()
+ return true
+ }
+ R.id.share_notes -> {
+ if (item == null) return false
+ val notes = item!!.description
+ if (!notes.isNullOrEmpty()) {
+ val shareText = if (Build.VERSION.SDK_INT >= 24) Html.fromHtml(notes, Html.FROM_HTML_MODE_LEGACY).toString()
+ else Html.fromHtml(notes).toString()
+ val context = requireContext()
+ val intent = ShareCompat.IntentBuilder(context)
+ .setType("text/plain")
+ .setText(shareText)
+ .setChooserTitle(R.string.share_notes_label)
+ .createChooserIntent()
+ context.startActivity(intent)
+ }
+ return true
+ }
+ else -> {
+ if (item == null) return false
+ return true
+ }
+ }
+ }
+
+ @UnstableApi override fun onResume() {
+ super.onResume()
+ updateAppearance()
+ }
+
+ @OptIn(UnstableApi::class) override fun onDestroyView() {
+ super.onDestroyView()
+ Log.d(TAG, "onDestroyView")
+ _binding = null
+ disposable?.dispose()
+ tts.shutdown()
+ }
+
+ @UnstableApi private fun updateAppearance() {
+ if (item == null) {
+ Log.d(TAG, "updateAppearance item is null")
+ return
+ }
+ onPrepareOptionsMenu(toolbar.menu)
+// FeedItemMenuHandler.onPrepareMenu(toolbar.menu, item, R.id.switch_home)
+ }
+
+ companion object {
+ private const val TAG = "EpisodeWebviewFragment"
+ private const val ARG_FEEDITEM = "feeditem"
+
+ @JvmStatic
+ fun newInstance(item: FeedItem): EpisodeHomeFragment {
+ val fragment = EpisodeHomeFragment()
+ val args = Bundle()
+ args.putSerializable(ARG_FEEDITEM, item)
+ fragment.arguments = args
+ return fragment
+ }
+ }
+}
diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt
index a769332b..d76b22f0 100644
--- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt
+++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt
@@ -85,12 +85,9 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private lateinit var imgvCover: ImageView
private lateinit var progbarDownload: CircularProgressBar
private lateinit var progbarLoading: ProgressBar
- private lateinit var butAction1Text: TextView
- private lateinit var butAction2Text: TextView
- private lateinit var butAction1Icon: ImageView
- private lateinit var butAction2Icon: ImageView
- private lateinit var butAction1: View
- private lateinit var butAction2: View
+ private lateinit var butAction0: View
+ private lateinit var butAction1: ImageView
+ private lateinit var butAction2: ImageView
private lateinit var noMediaLabel: View
private var actionButton1: ItemActionButton? = null
@@ -142,14 +139,15 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
imgvCover.setOnClickListener { openPodcast() }
progbarDownload = binding.circularProgressBar
progbarLoading = binding.progbarLoading
+ butAction0 = binding.butAction0
butAction1 = binding.butAction1
butAction2 = binding.butAction2
- butAction1Icon = binding.butAction1Icon
- butAction2Icon = binding.butAction2Icon
- butAction1Text = binding.butAction1Text
- butAction2Text = binding.butAction2Text
noMediaLabel = binding.noMediaLabel
+ butAction0.setOnClickListener(View.OnClickListener {
+ if (item?.link != null) (activity as MainActivity).loadChildFragment(EpisodeHomeFragment.newInstance(item!!))
+ })
+
butAction1.setOnClickListener(View.OnClickListener {
when {
actionButton1 is StreamActionButton && !UserPreferences.isStreamOverDownload
@@ -272,6 +270,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
if (webviewData != null && !itemsLoaded) {
webvDescription.loadDataWithBaseURL("https://127.0.0.1", webviewData!!, "text/html", "utf-8", "about:blank")
}
+// if (item?.link != null) binding.webView.loadUrl(item!!.link!!)
updateAppearance()
}
@@ -298,8 +297,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
val options: RequestOptions = RequestOptions()
.error(R.color.light_gray)
- .transform(FitCenter(),
- RoundedCorners((8 * resources.displayMetrics.density).toInt()))
+ .transform(FitCenter(), RoundedCorners((8 * resources.displayMetrics.density).toInt()))
.dontAnimate()
val imgLocFB = ImageResourceUtils.getFallbackImageLocation(item!!)
@@ -337,8 +335,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
noMediaLabel.visibility = View.GONE
if (media.getDuration() > 0) {
txtvDuration.text = Converter.getDurationStringLong(media.getDuration())
- txtvDuration.setContentDescription(
- Converter.getDurationStringLocalized(requireContext(), media.getDuration().toLong()))
+ txtvDuration.setContentDescription(Converter.getDurationStringLocalized(requireContext(), media.getDuration().toLong()))
}
if (item != null) {
actionButton1 = when {
@@ -377,17 +374,17 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
if (actionButton1 != null) {
- butAction1Text.setText(actionButton1!!.getLabel())
- butAction1Icon.setImageResource(actionButton1!!.getDrawable())
+// butAction1Text.setText(actionButton1!!.getLabel())
+ butAction1.setImageResource(actionButton1!!.getDrawable())
}
- butAction1Text.transformationMethod = null
+// butAction1Text.transformationMethod = null
if (actionButton1 != null) butAction1.visibility = actionButton1!!.visibility
if (actionButton2 != null) {
- butAction2Text.setText(actionButton2!!.getLabel())
- butAction2Icon.setImageResource(actionButton2!!.getDrawable())
+// butAction2Text.setText(actionButton2!!.getLabel())
+ butAction2.setImageResource(actionButton2!!.getDrawable())
}
- butAction2Text.transformationMethod = null
+// butAction2Text.transformationMethod = null
if (actionButton2 != null) butAction2.visibility = actionButton2!!.visibility
}
diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/PlayerDetailsFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/PlayerDetailsFragment.kt
index b2120f94..a4cc3e01 100644
--- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/PlayerDetailsFragment.kt
+++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/PlayerDetailsFragment.kt
@@ -4,7 +4,6 @@ import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.PlayerDetailsFragmentBinding
import ac.mdiq.podcini.feed.util.ImageResourceUtils
import ac.mdiq.podcini.playback.PlaybackController
-import ac.mdiq.podcini.util.event.playback.PlaybackPositionEvent
import ac.mdiq.podcini.storage.DBReader
import ac.mdiq.podcini.storage.model.feed.Chapter
import ac.mdiq.podcini.storage.model.feed.EmbeddedChapterImage
@@ -16,6 +15,7 @@ import ac.mdiq.podcini.ui.utils.ShownotesCleaner
import ac.mdiq.podcini.ui.view.ShownotesWebView
import ac.mdiq.podcini.util.ChapterUtils
import ac.mdiq.podcini.util.DateFormatter
+import ac.mdiq.podcini.util.event.playback.PlaybackPositionEvent
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
@@ -44,6 +44,7 @@ import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.load.resource.bitmap.FitCenter
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestOptions
+import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.snackbar.Snackbar
import io.reactivex.Maybe
import io.reactivex.MaybeEmitter
@@ -212,13 +213,9 @@ class PlayerDetailsFragment : Fragment() {
val lines = binding.txtvEpisodeTitle.lineCount
val animUnit = 1500
if (lines > binding.txtvEpisodeTitle.maxLines) {
- val titleHeight = (binding.txtvEpisodeTitle.height
- - binding.txtvEpisodeTitle.paddingTop
- - binding.txtvEpisodeTitle.paddingBottom)
- val verticalMarquee: ObjectAnimator = ObjectAnimator.ofInt(
- binding.txtvEpisodeTitle, "scrollY", 0,
- (lines - binding.txtvEpisodeTitle.maxLines) * (titleHeight / binding.txtvEpisodeTitle.maxLines))
- .setDuration((lines * animUnit).toLong())
+ val titleHeight = (binding.txtvEpisodeTitle.height - binding.txtvEpisodeTitle.paddingTop - binding.txtvEpisodeTitle.paddingBottom)
+ val verticalMarquee: ObjectAnimator = ObjectAnimator.ofInt(binding.txtvEpisodeTitle, "scrollY", 0,
+ (lines - binding.txtvEpisodeTitle.maxLines) * (titleHeight / binding.txtvEpisodeTitle.maxLines)).setDuration((lines * animUnit).toLong())
val fadeOut: ObjectAnimator = ObjectAnimator.ofFloat(binding.txtvEpisodeTitle, "alpha", 0f)
fadeOut.setStartDelay(animUnit.toLong())
fadeOut.addListener(object : AnimatorListenerAdapter() {
@@ -254,11 +251,8 @@ class PlayerDetailsFragment : Fragment() {
val newVisibility = if (chapterControlVisible) View.VISIBLE else View.GONE
if (binding.chapterButton.visibility != newVisibility) {
binding.chapterButton.visibility = newVisibility
- ObjectAnimator.ofFloat(binding.chapterButton,
- "alpha",
- (if (chapterControlVisible) 0 else 1).toFloat(),
- (if (chapterControlVisible) 1 else 0).toFloat())
- .start()
+ ObjectAnimator.ofFloat(binding.chapterButton, "alpha",
+ (if (chapterControlVisible) 0 else 1).toFloat(), (if (chapterControlVisible) 1 else 0).toFloat()).start()
}
}
@@ -279,8 +273,7 @@ class PlayerDetailsFragment : Fragment() {
if (media == null) return
val options: RequestOptions = RequestOptions()
.dontAnimate()
- .transform(FitCenter(),
- RoundedCorners((16 * resources.displayMetrics.density).toInt()))
+ .transform(FitCenter(), RoundedCorners((16 * resources.displayMetrics.density).toInt()))
val cover: RequestBuilder = Glide.with(this)
.load(media!!.getImageLocation())
@@ -308,9 +301,7 @@ class PlayerDetailsFragment : Fragment() {
private val currentChapter: Chapter?
get() {
- if (media == null || media!!.getChapters().isEmpty() || displayedChapterIndex == -1) {
- return null
- }
+ if (media == null || media!!.getChapters().isEmpty() || displayedChapterIndex == -1) return null
return media!!.getChapters()[displayedChapterIndex]
}
@@ -364,6 +355,8 @@ class PlayerDetailsFragment : Fragment() {
}
@UnstableApi private fun restoreFromPreference(): Boolean {
+ if ((activity as MainActivity).bottomSheet.state != BottomSheetBehavior.STATE_EXPANDED) return false
+
Log.d(TAG, "Restoring from preferences")
val activity: Activity? = activity
if (activity != null) {
@@ -411,18 +404,14 @@ class PlayerDetailsFragment : Fragment() {
// private fun configureForOrientation(newConfig: Configuration) {
// val isPortrait = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
//
-// binding.coverFragment.orientation = if (isPortrait) LinearLayout.VERTICAL else LinearLayout.HORIZONTAL
+//// binding.coverFragment.orientation = if (isPortrait) LinearLayout.VERTICAL else LinearLayout.HORIZONTAL
//
// if (isPortrait) {
-// binding.coverHolder.layoutParams =
-// LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f)
-// binding.coverFragmentTextContainer.layoutParams =
-// LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+// binding.coverHolder.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f)
+//// binding.coverFragmentTextContainer.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
// } else {
-// binding.coverHolder.layoutParams =
-// LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f)
-// binding.coverFragmentTextContainer.layoutParams =
-// LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f)
+// binding.coverHolder.layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f)
+//// binding.coverFragmentTextContainer.layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f)
// }
//
// (binding.episodeDetails.parent as ViewGroup).removeView(binding.episodeDetails)
@@ -442,8 +431,7 @@ class PlayerDetailsFragment : Fragment() {
val clipboardManager: ClipboardManager? = ContextCompat.getSystemService(requireContext(), ClipboardManager::class.java)
clipboardManager?.setPrimaryClip(ClipData.newPlainText("Podcini", text))
if (Build.VERSION.SDK_INT <= 32) {
- (requireActivity() as MainActivity).showSnackbarAbovePlayer(
- resources.getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT)
+ (requireActivity() as MainActivity).showSnackbarAbovePlayer(resources.getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT)
}
return true
}
diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/VideoEpisodeFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/VideoEpisodeFragment.kt
index 43dfb88b..36c62475 100644
--- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/VideoEpisodeFragment.kt
+++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/VideoEpisodeFragment.kt
@@ -12,6 +12,7 @@ import ac.mdiq.podcini.storage.DBReader
import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.storage.model.playback.Playable
import ac.mdiq.podcini.ui.activity.VideoplayerActivity
+import ac.mdiq.podcini.ui.activity.VideoplayerActivity.Companion.videoMode
import ac.mdiq.podcini.ui.activity.appstartintent.MainActivityStarter
import ac.mdiq.podcini.ui.dialog.SkipPreferenceDialog
import ac.mdiq.podcini.ui.utils.PictureInPictureUtil
@@ -343,7 +344,7 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener {
}
if (videoControlsShowing) {
hideVideoControls(false)
- if ((activity as VideoplayerActivity).videoMode == VideoplayerActivity.FULL_SCREEN_VIEW)
+ if (videoMode == VideoplayerActivity.VideoMode.FULL_SCREEN_VIEW)
(activity as AppCompatActivity).supportActionBar?.hide()
videoControlsShowing = false
}
@@ -360,7 +361,7 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener {
fun toggleVideoControlsVisibility() {
if (videoControlsShowing) {
hideVideoControls(true)
- if ((activity as VideoplayerActivity).videoMode == VideoplayerActivity.FULL_SCREEN_VIEW)
+ if (videoMode == VideoplayerActivity.VideoMode.FULL_SCREEN_VIEW)
(activity as AppCompatActivity).supportActionBar?.hide()
} else {
showVideoControls()
@@ -461,8 +462,8 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener {
if (videoControlsShowing) {
Log.d(TAG, "Hiding video controls")
hideVideoControls(true)
- if ((activity as VideoplayerActivity).videoMode == VideoplayerActivity.FULL_SCREEN_VIEW)
- (activity as? AppCompatActivity)?.supportActionBar?.hide()
+ if (videoMode == VideoplayerActivity.VideoMode.FULL_SCREEN_VIEW)
+ (activity as? AppCompatActivity)?.supportActionBar?.hide()
videoControlsShowing = false
}
}
diff --git a/app/src/main/java/ac/mdiq/podcini/ui/utils/ShownotesCleaner.kt b/app/src/main/java/ac/mdiq/podcini/ui/utils/ShownotesCleaner.kt
index 723146cf..ccc5c907 100644
--- a/app/src/main/java/ac/mdiq/podcini/ui/utils/ShownotesCleaner.kt
+++ b/app/src/main/java/ac/mdiq/podcini/ui/utils/ShownotesCleaner.kt
@@ -27,8 +27,7 @@ class ShownotesCleaner(context: Context, private val rawShownotes: String, priva
init {
val colorPrimary = colorToHtml(context, android.R.attr.textColorPrimary)
val colorAccent = colorToHtml(context, R.attr.colorAccent)
- val margin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f,
- context.resources.displayMetrics).toInt()
+ val margin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f, context.resources.displayMetrics).toInt()
var styleString: String? = ""
try {
val templateStream = context.assets.open("shownotes-style.css")
@@ -36,15 +35,13 @@ class ShownotesCleaner(context: Context, private val rawShownotes: String, priva
} catch (e: IOException) {
e.printStackTrace()
}
- webviewStyle = String.format(Locale.US, styleString!!, colorPrimary, colorAccent,
- margin, margin, margin, margin)
+ webviewStyle = String.format(Locale.US, styleString!!, colorPrimary, colorAccent, margin, margin, margin, margin)
}
private fun colorToHtml(context: Context, colorAttr: Int): String {
val res = context.theme.obtainStyledAttributes(intArrayOf(colorAttr))
@ColorInt val col = res.getColor(0, 0)
- val color = ("rgba(" + Color.red(col) + "," + Color.green(col) + ","
- + Color.blue(col) + "," + (Color.alpha(col) / 255.0) + ")")
+ val color = ("rgba(" + Color.red(col) + "," + Color.green(col) + "," + Color.blue(col) + "," + (Color.alpha(col) / 255.0) + ")")
res.recycle()
return color
}
diff --git a/app/src/main/java/ac/mdiq/podcini/ui/widget/WidgetUpdater.kt b/app/src/main/java/ac/mdiq/podcini/ui/widget/WidgetUpdater.kt
index 1e5a61a3..be13e7e6 100644
--- a/app/src/main/java/ac/mdiq/podcini/ui/widget/WidgetUpdater.kt
+++ b/app/src/main/java/ac/mdiq/podcini/ui/widget/WidgetUpdater.kt
@@ -40,9 +40,8 @@ object WidgetUpdater {
* Update the widgets with the given parameters. Must be called in a background thread.
*/
fun updateWidget(context: Context, widgetState: WidgetState?) {
- if (!isEnabled(context) || widgetState == null) {
- return
- }
+ if (!isEnabled(context) || widgetState == null) return
+
val startMediaPlayer = if (widgetState.media != null && widgetState.media.getMediaType() === MediaType.VIDEO) {
VideoPlayerActivityStarter(context).pendingIntent
} else {
@@ -88,8 +87,7 @@ object WidgetUpdater {
views.setViewVisibility(R.id.txtvTitle, View.VISIBLE)
views.setViewVisibility(R.id.txtNoPlaying, View.GONE)
- val progressString = getProgressString(widgetState.position,
- widgetState.duration, widgetState.playbackSpeed)
+ val progressString = getProgressString(widgetState.position, widgetState.duration, widgetState.playbackSpeed)
if (progressString != null) {
views.setViewVisibility(R.id.txtvProgress, View.VISIBLE)
views.setTextViewText(R.id.txtvProgress, progressString)
@@ -106,22 +104,16 @@ object WidgetUpdater {
views.setImageViewResource(R.id.butPlayExtended, R.drawable.ic_widget_play)
views.setContentDescription(R.id.butPlayExtended, context.getString(R.string.play_label))
}
- views.setOnClickPendingIntent(R.id.butPlay,
- createPendingIntent(context, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE))
- views.setOnClickPendingIntent(R.id.butPlayExtended,
- createPendingIntent(context, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE))
- views.setOnClickPendingIntent(R.id.butRew,
- createPendingIntent(context, KeyEvent.KEYCODE_MEDIA_REWIND))
- views.setOnClickPendingIntent(R.id.butFastForward,
- createPendingIntent(context, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD))
- views.setOnClickPendingIntent(R.id.butSkip,
- createPendingIntent(context, KeyEvent.KEYCODE_MEDIA_NEXT))
+ views.setOnClickPendingIntent(R.id.butPlay, createPendingIntent(context, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE))
+ views.setOnClickPendingIntent(R.id.butPlayExtended, createPendingIntent(context, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE))
+ views.setOnClickPendingIntent(R.id.butRew, createPendingIntent(context, KeyEvent.KEYCODE_MEDIA_REWIND))
+ views.setOnClickPendingIntent(R.id.butFastForward, createPendingIntent(context, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD))
+ views.setOnClickPendingIntent(R.id.butSkip, createPendingIntent(context, KeyEvent.KEYCODE_MEDIA_NEXT))
} else {
// start the app if they click anything
views.setOnClickPendingIntent(R.id.layout_left, startMediaPlayer)
views.setOnClickPendingIntent(R.id.butPlay, startMediaPlayer)
- views.setOnClickPendingIntent(R.id.butPlayExtended,
- createPendingIntent(context, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE))
+ views.setOnClickPendingIntent(R.id.butPlayExtended, createPendingIntent(context, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE))
views.setViewVisibility(R.id.txtvProgress, View.GONE)
views.setViewVisibility(R.id.txtvTitle, View.GONE)
views.setViewVisibility(R.id.txtNoPlaying, View.VISIBLE)
@@ -183,27 +175,18 @@ object WidgetUpdater {
}
private fun getProgressString(position: Int, duration: Int, speed: Float): String? {
- if (position < 0 || duration <= 0) {
- return null
- }
+ if (position < 0 || duration <= 0) return null
+
val converter = TimeSpeedConverter(speed)
return if (shouldShowRemainingTime()) {
- (getDurationStringLong(converter.convert(position)) + " / -"
- + getDurationStringLong(converter.convert(max(0.0,
- (duration - position).toDouble())
- .toInt())))
+ ("${getDurationStringLong(converter.convert(position))} / -${getDurationStringLong(converter.convert(max(0.0, (duration - position).toDouble()).toInt()))
+ }")
} else {
- (getDurationStringLong(converter.convert(position)) + " / "
- + getDurationStringLong(converter.convert(duration)))
+ (getDurationStringLong(converter.convert(position)) + " / " + getDurationStringLong(converter.convert(duration)))
}
}
- class WidgetState(val media: Playable?,
- val status: PlayerStatus,
- val position: Int,
- val duration: Int,
- val playbackSpeed: Float
- ) {
+ class WidgetState(val media: Playable?, val status: PlayerStatus, val position: Int, val duration: Int, val playbackSpeed: Float) {
constructor(status: PlayerStatus) : this(null, status, Playable.INVALID_TIME, Playable.INVALID_TIME, 1.0f)
}
}
diff --git a/app/src/main/res/drawable/baseline_home_24.xml b/app/src/main/res/drawable/baseline_home_24.xml
new file mode 100644
index 00000000..57d515fa
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_home_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/baseline_home_work_24.xml b/app/src/main/res/drawable/baseline_home_work_24.xml
new file mode 100644
index 00000000..b9092237
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_home_work_24.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/app/src/main/res/layout/episode_home_fragment.xml b/app/src/main/res/layout/episode_home_fragment.xml
new file mode 100644
index 00000000..8400d222
--- /dev/null
+++ b/app/src/main/res/layout/episode_home_fragment.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/episode_info_fragment.xml b/app/src/main/res/layout/episode_info_fragment.xml
index e200193a..823b9f36 100644
--- a/app/src/main/res/layout/episode_info_fragment.xml
+++ b/app/src/main/res/layout/episode_info_fragment.xml
@@ -114,80 +114,53 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
- android:gravity="center_vertical"
+ android:gravity="center"
android:baselineAligned="false">
-
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_marginTop="12dp"
+ android:layout_marginBottom="12dp"
+ tools:src="@drawable/ic_settings" />
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -210,10 +183,17 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/menu/episode_home.xml b/app/src/main/res/menu/episode_home.xml
new file mode 100644
index 00000000..9ba29f01
--- /dev/null
+++ b/app/src/main/res/menu/episode_home.xml
@@ -0,0 +1,21 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/mediaplayer.xml b/app/src/main/res/menu/mediaplayer.xml
index 4534dd98..594888c8 100644
--- a/app/src/main/res/menu/mediaplayer.xml
+++ b/app/src/main/res/menu/mediaplayer.xml
@@ -2,6 +2,14 @@