4.9.5 commit
This commit is contained in:
parent
3566f60b3e
commit
45e5fbef88
|
@ -158,8 +158,8 @@ android {
|
||||||
// Version code schema (not used):
|
// Version code schema (not used):
|
||||||
// "1.2.3-beta4" -> 1020304
|
// "1.2.3-beta4" -> 1020304
|
||||||
// "1.2.3" -> 1020395
|
// "1.2.3" -> 1020395
|
||||||
versionCode 3020135
|
versionCode 3020136
|
||||||
versionName "4.9.4"
|
versionName "4.9.5"
|
||||||
|
|
||||||
def commit = ""
|
def commit = ""
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -165,9 +165,11 @@ abstract class PlaybackController(private val activity: FragmentActivity) {
|
||||||
Log.d(TAG, "Received statusUpdate Intent.")
|
Log.d(TAG, "Received statusUpdate Intent.")
|
||||||
if (playbackService != null) {
|
if (playbackService != null) {
|
||||||
val info = playbackService!!.pSMPInfo
|
val info = playbackService!!.pSMPInfo
|
||||||
|
if (status != info.playerStatus || media != info.playable) {
|
||||||
status = info.playerStatus
|
status = info.playerStatus
|
||||||
media = info.playable
|
media = info.playable
|
||||||
handleStatus()
|
handleStatus()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "Couldn't receive status update: playbackService was null")
|
Log.w(TAG, "Couldn't receive status update: playbackService was null")
|
||||||
if (PlaybackService.isRunning) {
|
if (PlaybackService.isRunning) {
|
||||||
|
|
|
@ -14,8 +14,7 @@ import android.util.Log
|
||||||
import android.view.SurfaceHolder
|
import android.view.SurfaceHolder
|
||||||
import androidx.core.util.Consumer
|
import androidx.core.util.Consumer
|
||||||
import androidx.media3.common.*
|
import androidx.media3.common.*
|
||||||
import androidx.media3.common.Player.DiscontinuityReason
|
import androidx.media3.common.Player.*
|
||||||
import androidx.media3.common.Player.PositionInfo
|
|
||||||
import androidx.media3.common.TrackSelectionParameters.AudioOffloadPreferences
|
import androidx.media3.common.TrackSelectionParameters.AudioOffloadPreferences
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.datasource.DataSource
|
import androidx.media3.datasource.DataSource
|
||||||
|
@ -65,11 +64,14 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
|
||||||
private fun createPlayer() {
|
private fun createPlayer() {
|
||||||
if (exoPlayer == null) createStaticPlayer(context)
|
if (exoPlayer == null) createStaticPlayer(context)
|
||||||
|
|
||||||
exoPlayer?.addListener(object : Player.Listener {
|
exoPlayer?.addListener(object : Listener {
|
||||||
override fun onPlaybackStateChanged(playbackState: @Player.State Int) {
|
override fun onPlaybackStateChanged(playbackState: @State Int) {
|
||||||
when {
|
when (playbackState) {
|
||||||
audioCompletionListener != null && playbackState == Player.STATE_ENDED -> audioCompletionListener?.run()
|
STATE_ENDED -> {
|
||||||
playbackState == Player.STATE_BUFFERING -> bufferingUpdateListener?.accept(BUFFERING_STARTED)
|
exoPlayer?.seekTo(C.TIME_UNSET)
|
||||||
|
if (audioCompletionListener != null) audioCompletionListener?.run()
|
||||||
|
}
|
||||||
|
STATE_BUFFERING -> bufferingUpdateListener?.accept(BUFFERING_STARTED)
|
||||||
else -> bufferingUpdateListener?.accept(BUFFERING_ENDED)
|
else -> bufferingUpdateListener?.accept(BUFFERING_ENDED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,7 +90,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()
|
if (reason == DISCONTINUITY_REASON_SEEK) audioSeekCompleteListener?.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAudioSessionIdChanged(audioSessionId: Int) {
|
override fun onAudioSessionIdChanged(audioSessionId: Int) {
|
||||||
|
@ -112,7 +114,8 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
val isPlaying: Boolean
|
val isPlaying: Boolean
|
||||||
get() = exoPlayer!!.playWhenReady
|
get() = exoPlayer!!.isPlaying
|
||||||
|
// get() = exoPlayer!!.playWhenReady
|
||||||
|
|
||||||
fun pause() {
|
fun pause() {
|
||||||
exoPlayer?.pause()
|
exoPlayer?.pause()
|
||||||
|
@ -205,7 +208,7 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
if (exoPlayer?.playbackState == Player.STATE_IDLE) prepare()
|
if (exoPlayer?.playbackState == STATE_IDLE || exoPlayer?.playbackState == STATE_ENDED ) prepare()
|
||||||
|
|
||||||
exoPlayer?.play()
|
exoPlayer?.play()
|
||||||
// Can't set params when paused - so always set it on start in case they changed
|
// Can't set params when paused - so always set it on start in case they changed
|
||||||
|
@ -258,8 +261,9 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
|
||||||
get() {
|
get() {
|
||||||
val trackSelections = exoPlayer!!.currentTrackSelections
|
val trackSelections = exoPlayer!!.currentTrackSelections
|
||||||
val availableFormats = formats
|
val availableFormats = formats
|
||||||
|
Log.d(TAG, "selectedAudioTrack called tracks: ${trackSelections.length} formats: ${availableFormats.size}")
|
||||||
for (i in 0 until trackSelections.length) {
|
for (i in 0 until trackSelections.length) {
|
||||||
val track = trackSelections[i] as ExoTrackSelection? ?: continue
|
val track = trackSelections[i] as? ExoTrackSelection ?: continue
|
||||||
if (availableFormats.contains(track.selectedFormat)) return availableFormats.indexOf(track.selectedFormat)
|
if (availableFormats.contains(track.selectedFormat)) return availableFormats.indexOf(track.selectedFormat)
|
||||||
}
|
}
|
||||||
return -1
|
return -1
|
||||||
|
|
|
@ -125,7 +125,7 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
// stop playback of this episode
|
// stop playback of this episode
|
||||||
if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PLAYING || playerStatus == PlayerStatus.PREPARED)
|
if (playerStatus == PlayerStatus.PAUSED || (playerStatus == PlayerStatus.PLAYING) || playerStatus == PlayerStatus.PREPARED)
|
||||||
playerWrapper?.stop()
|
playerWrapper?.stop()
|
||||||
|
|
||||||
// set temporarily to pause in order to update list with current position
|
// set temporarily to pause in order to update list with current position
|
||||||
|
@ -168,8 +168,7 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val uiModeManager = context.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
|
val uiModeManager = context.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
|
||||||
if (uiModeManager.currentModeType != Configuration.UI_MODE_TYPE_CAR) setPlayerStatus(PlayerStatus.INITIALIZED,
|
if (uiModeManager.currentModeType != Configuration.UI_MODE_TYPE_CAR) setPlayerStatus(PlayerStatus.INITIALIZED, this.playable)
|
||||||
this.playable)
|
|
||||||
|
|
||||||
if (prepareImmediately) {
|
if (prepareImmediately) {
|
||||||
setPlayerStatus(PlayerStatus.PREPARING, this.playable)
|
setPlayerStatus(PlayerStatus.PREPARING, this.playable)
|
||||||
|
@ -276,8 +275,8 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
||||||
if (playerWrapper != null && mediaType == MediaType.VIDEO) videoSize = Pair(playerWrapper!!.videoWidth, playerWrapper!!.videoHeight)
|
if (playerWrapper != null && mediaType == MediaType.VIDEO) videoSize = Pair(playerWrapper!!.videoWidth, playerWrapper!!.videoHeight)
|
||||||
|
|
||||||
if (playable != null) {
|
if (playable != null) {
|
||||||
// TODO this call has no effect!
|
val pos = playable!!.getPosition()
|
||||||
if (playable!!.getPosition() > 0) seekTo(playable!!.getPosition())
|
if (pos > 0) seekTo(pos)
|
||||||
|
|
||||||
if (playable!!.getDuration() <= 0) {
|
if (playable!!.getDuration() <= 0) {
|
||||||
Log.d(TAG, "Setting duration of media")
|
Log.d(TAG, "Setting duration of media")
|
||||||
|
@ -318,8 +317,10 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
||||||
|
|
||||||
if (t >= getDuration()) {
|
if (t >= getDuration()) {
|
||||||
Log.d(TAG, "Seek reached end of file, skipping to next episode")
|
Log.d(TAG, "Seek reached end of file, skipping to next episode")
|
||||||
|
// TODO: test
|
||||||
|
playerWrapper?.seekTo(t)
|
||||||
endPlayback(true, wasSkipped = true, true, toStoppedState = true)
|
endPlayback(true, wasSkipped = true, true, toStoppedState = true)
|
||||||
return
|
// return
|
||||||
}
|
}
|
||||||
|
|
||||||
when (playerStatus) {
|
when (playerStatus) {
|
||||||
|
@ -367,7 +368,8 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
||||||
*/
|
*/
|
||||||
override fun getDuration(): Int {
|
override fun getDuration(): Int {
|
||||||
var retVal = Playable.INVALID_TIME
|
var retVal = Playable.INVALID_TIME
|
||||||
if (playerStatus == PlayerStatus.PLAYING || playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PREPARED) {
|
if ((playerStatus == PlayerStatus.PLAYING)
|
||||||
|
|| playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PREPARED) {
|
||||||
if (playerWrapper != null) retVal = playerWrapper!!.duration
|
if (playerWrapper != null) retVal = playerWrapper!!.duration
|
||||||
}
|
}
|
||||||
if (retVal <= 0 && playable != null && playable!!.getDuration() > 0) retVal = playable!!.getDuration()
|
if (retVal <= 0 && playable != null && playable!!.getDuration() > 0) retVal = playable!!.getDuration()
|
||||||
|
@ -379,6 +381,7 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
||||||
*/
|
*/
|
||||||
override fun getPosition(): Int {
|
override fun getPosition(): Int {
|
||||||
var retVal = Playable.INVALID_TIME
|
var retVal = Playable.INVALID_TIME
|
||||||
|
// TODO: test
|
||||||
if (playerStatus.isAtLeast(PlayerStatus.PREPARED)) {
|
if (playerStatus.isAtLeast(PlayerStatus.PREPARED)) {
|
||||||
if (playerWrapper != null) retVal = playerWrapper!!.currentPosition
|
if (playerWrapper != null) retVal = playerWrapper!!.currentPosition
|
||||||
}
|
}
|
||||||
|
@ -409,7 +412,8 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
||||||
*/
|
*/
|
||||||
override fun getPlaybackSpeed(): Float {
|
override fun getPlaybackSpeed(): Float {
|
||||||
var retVal = 1f
|
var retVal = 1f
|
||||||
if (playerStatus == PlayerStatus.PLAYING || playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.INITIALIZED || playerStatus == PlayerStatus.PREPARED) {
|
if (playerStatus == PlayerStatus.PLAYING|| playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.INITIALIZED
|
||||||
|
|| playerStatus == PlayerStatus.PREPARED) {
|
||||||
if (playerWrapper != null) retVal = playerWrapper!!.currentSpeedMultiplier
|
if (playerWrapper != null) retVal = playerWrapper!!.currentSpeedMultiplier
|
||||||
}
|
}
|
||||||
return retVal
|
return retVal
|
||||||
|
@ -622,6 +626,8 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
||||||
shouldContinue || toStoppedState -> {
|
shouldContinue || toStoppedState -> {
|
||||||
if (nextMedia == null) {
|
if (nextMedia == null) {
|
||||||
callback.onPlaybackEnded(null, true)
|
callback.onPlaybackEnded(null, true)
|
||||||
|
playable = null
|
||||||
|
ExoPlayerWrapper.exoPlayer?.stop()
|
||||||
stop()
|
stop()
|
||||||
}
|
}
|
||||||
val hasNext = nextMedia != null
|
val hasNext = nextMedia != null
|
||||||
|
@ -660,6 +666,7 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
mp.setOnErrorListener(Consumer { message: String ->
|
mp.setOnErrorListener(Consumer { message: String ->
|
||||||
|
Log.e(TAG, "PlayerErrorEvent: $message")
|
||||||
EventBus.getDefault().postSticky(PlayerErrorEvent(message))
|
EventBus.getDefault().postSticky(PlayerErrorEvent(message))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,12 +39,8 @@ import ac.mdiq.podcini.preferences.UserPreferences.rewindSecs
|
||||||
import ac.mdiq.podcini.preferences.UserPreferences.setPlaybackSpeed
|
import ac.mdiq.podcini.preferences.UserPreferences.setPlaybackSpeed
|
||||||
import ac.mdiq.podcini.preferences.UserPreferences.shouldFavoriteKeepEpisode
|
import ac.mdiq.podcini.preferences.UserPreferences.shouldFavoriteKeepEpisode
|
||||||
import ac.mdiq.podcini.preferences.UserPreferences.shouldSkipKeepEpisode
|
import ac.mdiq.podcini.preferences.UserPreferences.shouldSkipKeepEpisode
|
||||||
import ac.mdiq.podcini.preferences.UserPreferences.showNextChapterOnFullNotification
|
|
||||||
import ac.mdiq.podcini.preferences.UserPreferences.showPlaybackSpeedOnFullNotification
|
|
||||||
import ac.mdiq.podcini.preferences.UserPreferences.showSkipOnFullNotification
|
|
||||||
import ac.mdiq.podcini.preferences.UserPreferences.videoPlaybackSpeed
|
import ac.mdiq.podcini.preferences.UserPreferences.videoPlaybackSpeed
|
||||||
import ac.mdiq.podcini.receiver.MediaButtonReceiver
|
import ac.mdiq.podcini.receiver.MediaButtonReceiver
|
||||||
import ac.mdiq.podcini.service.playback.WearMediaSession
|
|
||||||
import ac.mdiq.podcini.storage.DBReader
|
import ac.mdiq.podcini.storage.DBReader
|
||||||
import ac.mdiq.podcini.storage.DBWriter
|
import ac.mdiq.podcini.storage.DBWriter
|
||||||
import ac.mdiq.podcini.storage.model.feed.FeedItem
|
import ac.mdiq.podcini.storage.model.feed.FeedItem
|
||||||
|
@ -67,21 +63,16 @@ import ac.mdiq.podcini.util.event.playback.*
|
||||||
import ac.mdiq.podcini.util.event.settings.SkipIntroEndingChangedEvent
|
import ac.mdiq.podcini.util.event.settings.SkipIntroEndingChangedEvent
|
||||||
import ac.mdiq.podcini.util.event.settings.SpeedPresetChangedEvent
|
import ac.mdiq.podcini.util.event.settings.SpeedPresetChangedEvent
|
||||||
import ac.mdiq.podcini.util.event.settings.VolumeAdaptionChangedEvent
|
import ac.mdiq.podcini.util.event.settings.VolumeAdaptionChangedEvent
|
||||||
import android.Manifest
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.app.PendingIntent.FLAG_IMMUTABLE
|
|
||||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
|
||||||
import android.bluetooth.BluetoothA2dp
|
import android.bluetooth.BluetoothA2dp
|
||||||
import android.content.*
|
import android.content.*
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.os.*
|
import android.os.*
|
||||||
import android.os.Build.VERSION_CODES
|
import android.os.Build.VERSION_CODES
|
||||||
import android.service.quicksettings.TileService
|
import android.service.quicksettings.TileService
|
||||||
import android.support.v4.media.MediaMetadataCompat
|
|
||||||
import android.support.v4.media.session.MediaSessionCompat
|
import android.support.v4.media.session.MediaSessionCompat
|
||||||
import android.support.v4.media.session.PlaybackStateCompat
|
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.Pair
|
import android.util.Pair
|
||||||
|
@ -89,13 +80,17 @@ import android.view.KeyEvent
|
||||||
import android.view.SurfaceHolder
|
import android.view.SurfaceHolder
|
||||||
import android.webkit.URLUtil
|
import android.webkit.URLUtil
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.app.ActivityCompat
|
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.media3.common.MediaItem
|
||||||
|
import androidx.media3.common.MediaMetadata
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import androidx.media3.common.Player.*
|
import androidx.media3.common.Player.STATE_ENDED
|
||||||
|
import androidx.media3.common.Player.STATE_IDLE
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.session.*
|
import androidx.media3.session.MediaSession
|
||||||
import com.google.common.collect.ImmutableList
|
import androidx.media3.session.MediaSessionService
|
||||||
|
import androidx.media3.session.SessionCommand
|
||||||
|
import androidx.media3.session.SessionResult
|
||||||
import com.google.common.util.concurrent.Futures
|
import com.google.common.util.concurrent.Futures
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
|
@ -189,7 +184,6 @@ class PlaybackService : MediaSessionService() {
|
||||||
if (ExoPlayerWrapper.exoPlayer == null) ExoPlayerWrapper.createStaticPlayer(applicationContext)
|
if (ExoPlayerWrapper.exoPlayer == null) ExoPlayerWrapper.createStaticPlayer(applicationContext)
|
||||||
mediaSession = MediaSession.Builder(applicationContext, ExoPlayerWrapper.exoPlayer!!)
|
mediaSession = MediaSession.Builder(applicationContext, ExoPlayerWrapper.exoPlayer!!)
|
||||||
.setCallback(MyCallback())
|
.setCallback(MyCallback())
|
||||||
// .setCustomLayout(customMediaNotificationProvider.notificationMediaButtons)
|
|
||||||
.setCustomLayout(notificationCustomButtons)
|
.setCustomLayout(notificationCustomButtons)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
@ -202,7 +196,7 @@ class PlaybackService : MediaSessionService() {
|
||||||
if (mediaPlayer != null) {
|
if (mediaPlayer != null) {
|
||||||
media = mediaPlayer!!.getPlayable()
|
media = mediaPlayer!!.getPlayable()
|
||||||
wasPlaying = mediaPlayer!!.playerStatus == PlayerStatus.PLAYING || mediaPlayer!!.playerStatus == PlayerStatus.FALLBACK
|
wasPlaying = mediaPlayer!!.playerStatus == PlayerStatus.PLAYING || mediaPlayer!!.playerStatus == PlayerStatus.FALLBACK
|
||||||
mediaPlayer!!.pause(true, false)
|
mediaPlayer!!.pause(abandonFocus = true, reinit = false)
|
||||||
mediaPlayer!!.shutdown()
|
mediaPlayer!!.shutdown()
|
||||||
}
|
}
|
||||||
mediaPlayer = CastPsmp.getInstanceIfConnected(this, mediaPlayerCallback)
|
mediaPlayer = CastPsmp.getInstanceIfConnected(this, mediaPlayerCallback)
|
||||||
|
@ -215,7 +209,7 @@ class PlaybackService : MediaSessionService() {
|
||||||
Log.d(TAG, "onTaskRemoved")
|
Log.d(TAG, "onTaskRemoved")
|
||||||
val player = mediaSession?.player
|
val player = mediaSession?.player
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
if (!player.playWhenReady || player.mediaItemCount == 0 || player.playbackState == Player.STATE_ENDED) {
|
if (!player.playWhenReady || player.mediaItemCount == 0 || player.playbackState == STATE_ENDED) {
|
||||||
// Stop the service if not playing, continue playing in the background
|
// Stop the service if not playing, continue playing in the background
|
||||||
// otherwise.
|
// otherwise.
|
||||||
stopSelf()
|
stopSelf()
|
||||||
|
@ -251,7 +245,7 @@ class PlaybackService : MediaSessionService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isServiceReady(): Boolean {
|
fun isServiceReady(): Boolean {
|
||||||
return mediaSession?.player?.playbackState != STATE_IDLE
|
return mediaSession?.player?.playbackState != STATE_IDLE && mediaSession?.player?.playbackState != STATE_ENDED
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class MyCallback : MediaSession.Callback {
|
private inner class MyCallback : MediaSession.Callback {
|
||||||
|
@ -345,9 +339,7 @@ class PlaybackService : MediaSessionService() {
|
||||||
{ obj: Throwable -> obj.printStackTrace() })
|
{ obj: Throwable -> obj.printStackTrace() })
|
||||||
}
|
}
|
||||||
|
|
||||||
// private fun createBrowsableMediaItem(
|
// private fun createBrowsableMediaItem(@StringRes title: Int, @DrawableRes icon: Int, numEpisodes: Int): MediaItem {
|
||||||
// @StringRes title: Int, @DrawableRes icon: Int, numEpisodes: Int
|
|
||||||
// ): MediaBrowserCompat.MediaItem {
|
|
||||||
// val uri = Uri.Builder()
|
// val uri = Uri.Builder()
|
||||||
// .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
|
// .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
|
||||||
// .authority(resources.getResourcePackageName(icon))
|
// .authority(resources.getResourcePackageName(icon))
|
||||||
|
@ -355,17 +347,17 @@ class PlaybackService : MediaSessionService() {
|
||||||
// .appendPath(resources.getResourceEntryName(icon))
|
// .appendPath(resources.getResourceEntryName(icon))
|
||||||
// .build()
|
// .build()
|
||||||
//
|
//
|
||||||
// val description = MediaDescriptionCompat.Builder()
|
// val description = MediaDescription.Builder()
|
||||||
// .setIconUri(uri)
|
// .setIconUri(uri)
|
||||||
// .setMediaId(resources.getString(title))
|
// .setMediaId(resources.getString(title))
|
||||||
// .setTitle(resources.getString(title))
|
// .setTitle(resources.getString(title))
|
||||||
// .setSubtitle(resources.getQuantityString(R.plurals.num_episodes, numEpisodes, numEpisodes))
|
// .setSubtitle(resources.getQuantityString(R.plurals.num_episodes, numEpisodes, numEpisodes))
|
||||||
// .build()
|
// .build()
|
||||||
// return MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
|
// return MediaItem(description, MediaItem.FLAG_BROWSABLE)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// private fun createBrowsableMediaItemForFeed(feed: Feed): MediaBrowserCompat.MediaItem {
|
// private fun createBrowsableMediaItemForFeed(feed: Feed): MediaItem {
|
||||||
// val builder = MediaDescriptionCompat.Builder()
|
// val builder = MediaDescription.Builder()
|
||||||
// .setMediaId("FeedId:" + feed.id)
|
// .setMediaId("FeedId:" + feed.id)
|
||||||
// .setTitle(feed.title)
|
// .setTitle(feed.title)
|
||||||
// .setDescription(feed.description)
|
// .setDescription(feed.description)
|
||||||
|
@ -377,13 +369,10 @@ class PlaybackService : MediaSessionService() {
|
||||||
// builder.setMediaUri(Uri.parse(feed.link))
|
// builder.setMediaUri(Uri.parse(feed.link))
|
||||||
// }
|
// }
|
||||||
// val description = builder.build()
|
// val description = builder.build()
|
||||||
// return MediaBrowserCompat.MediaItem(description,
|
// return MediaItem(description, MediaItem.FLAG_BROWSABLE)
|
||||||
// MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// override fun onLoadChildren(parentId: String,
|
// override fun onLoadChildren(parentId: String, result: Result<List<MediaItem>>) {
|
||||||
// result: Result<List<MediaBrowserCompat.MediaItem>>
|
|
||||||
// ) {
|
|
||||||
// Log.d(TAG, "OnLoadChildren: parentMediaId=$parentId")
|
// Log.d(TAG, "OnLoadChildren: parentMediaId=$parentId")
|
||||||
// result.detach()
|
// result.detach()
|
||||||
//
|
//
|
||||||
|
@ -475,12 +464,14 @@ class PlaybackService : MediaSessionService() {
|
||||||
|
|
||||||
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)
|
super.onStartCommand(intent, flags, startId)
|
||||||
Log.d(TAG, "OnStartCommand called")
|
// Log.d(TAG, "OnStartCommand called")
|
||||||
|
|
||||||
val keycode = intent?.getIntExtra(MediaButtonReceiver.EXTRA_KEYCODE, -1) ?: -1
|
val keycode = intent?.getIntExtra(MediaButtonReceiver.EXTRA_KEYCODE, -1) ?: -1
|
||||||
val customAction = intent?.getStringExtra(MediaButtonReceiver.EXTRA_CUSTOM_ACTION)
|
val customAction = intent?.getStringExtra(MediaButtonReceiver.EXTRA_CUSTOM_ACTION)
|
||||||
val hardwareButton = intent?.getBooleanExtra(MediaButtonReceiver.EXTRA_HARDWAREBUTTON, false) ?: false
|
val hardwareButton = intent?.getBooleanExtra(MediaButtonReceiver.EXTRA_HARDWAREBUTTON, false) ?: false
|
||||||
val playable = intent?.getParcelableExtra<Playable>(PlaybackServiceInterface.EXTRA_PLAYABLE)
|
val playable = intent?.getParcelableExtra<Playable>(PlaybackServiceInterface.EXTRA_PLAYABLE)
|
||||||
|
Log.d(TAG, "OnStartCommand $keycode $customAction $hardwareButton $playable")
|
||||||
|
|
||||||
if (keycode == -1 && playable == null && customAction == null) {
|
if (keycode == -1 && playable == null && customAction == null) {
|
||||||
Log.e(TAG, "PlaybackService was started with no arguments")
|
Log.e(TAG, "PlaybackService was started with no arguments")
|
||||||
return START_NOT_STICKY
|
return START_NOT_STICKY
|
||||||
|
@ -556,37 +547,38 @@ class PlaybackService : MediaSessionService() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// val intentAllowThisTime = Intent(originalIntent)
|
val intentAllowThisTime = Intent(originalIntent)
|
||||||
// intentAllowThisTime.setAction(PlaybackServiceInterface.EXTRA_ALLOW_STREAM_THIS_TIME)
|
intentAllowThisTime.setAction(PlaybackServiceInterface.EXTRA_ALLOW_STREAM_THIS_TIME)
|
||||||
// intentAllowThisTime.putExtra(PlaybackServiceInterface.EXTRA_ALLOW_STREAM_THIS_TIME, true)
|
intentAllowThisTime.putExtra(PlaybackServiceInterface.EXTRA_ALLOW_STREAM_THIS_TIME, true)
|
||||||
// val pendingIntentAllowThisTime = if (Build.VERSION.SDK_INT >= VERSION_CODES.O)
|
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)
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||||
// else PendingIntent.getService(this, R.id.pending_intent_allow_stream_this_time, intentAllowThisTime,
|
else PendingIntent.getService(this, R.id.pending_intent_allow_stream_this_time, intentAllowThisTime,
|
||||||
// PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||||
|
|
||||||
// val intentAlwaysAllow = Intent(intentAllowThisTime)
|
val intentAlwaysAllow = Intent(intentAllowThisTime)
|
||||||
// intentAlwaysAllow.setAction(PlaybackServiceInterface.EXTRA_ALLOW_STREAM_ALWAYS)
|
intentAlwaysAllow.setAction(PlaybackServiceInterface.EXTRA_ALLOW_STREAM_ALWAYS)
|
||||||
// intentAlwaysAllow.putExtra(PlaybackServiceInterface.EXTRA_ALLOW_STREAM_ALWAYS, true)
|
intentAlwaysAllow.putExtra(PlaybackServiceInterface.EXTRA_ALLOW_STREAM_ALWAYS, true)
|
||||||
// val pendingIntentAlwaysAllow = if (Build.VERSION.SDK_INT >= VERSION_CODES.O)
|
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)
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||||
// else PendingIntent.getService(this, R.id.pending_intent_allow_stream_always, intentAlwaysAllow,
|
else PendingIntent.getService(this, R.id.pending_intent_allow_stream_always, intentAlwaysAllow,
|
||||||
// PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||||
|
|
||||||
|
val builder = NotificationCompat.Builder(this, NotificationUtils.CHANNEL_ID_USER_ACTION)
|
||||||
|
.setSmallIcon(R.drawable.ic_notification_stream)
|
||||||
|
.setContentTitle(getString(R.string.confirm_mobile_streaming_notification_title))
|
||||||
|
.setContentText(getString(R.string.confirm_mobile_streaming_notification_message))
|
||||||
|
.setStyle(NotificationCompat.BigTextStyle()
|
||||||
|
.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)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
|
||||||
// val builder = NotificationCompat.Builder(this,
|
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
// NotificationUtils.CHANNEL_ID_USER_ACTION)
|
notificationManager.notify(5566, builder.build())
|
||||||
// .setSmallIcon(R.drawable.ic_notification_stream)
|
|
||||||
// .setContentTitle(getString(R.string.confirm_mobile_streaming_notification_title))
|
|
||||||
// .setContentText(getString(R.string.confirm_mobile_streaming_notification_message))
|
|
||||||
// .setStyle(NotificationCompat.BigTextStyle()
|
|
||||||
// .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)
|
|
||||||
// .setAutoCancel(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -670,7 +662,8 @@ class PlaybackService : MediaSessionService() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
KeyEvent.KEYCODE_MEDIA_STOP -> {
|
KeyEvent.KEYCODE_MEDIA_STOP -> {
|
||||||
if (this.status == PlayerStatus.FALLBACK || status == PlayerStatus.PLAYING) mediaPlayer?.pause(true, true)
|
if (this.status == PlayerStatus.FALLBACK || status == PlayerStatus.PLAYING)
|
||||||
|
mediaPlayer?.pause(abandonFocus = true, reinit = true)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
|
@ -705,7 +698,7 @@ class PlaybackService : MediaSessionService() {
|
||||||
val localFeed = URLUtil.isContentUrl(playable.getStreamUrl())
|
val localFeed = URLUtil.isContentUrl(playable.getStreamUrl())
|
||||||
val stream = !playable.localFileAvailable() || localFeed
|
val stream = !playable.localFileAvailable() || localFeed
|
||||||
if (stream && !localFeed && !isStreamingAllowed && !allowStreamThisTime) {
|
if (stream && !localFeed && !isStreamingAllowed && !allowStreamThisTime) {
|
||||||
// displayStreamingNotAllowedNotification(PlaybackServiceStarter(this, playable).intent)
|
displayStreamingNotAllowedNotification(PlaybackServiceStarter(this, playable).intent)
|
||||||
writeNoMediaPlaying()
|
writeNoMediaPlaying()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -716,8 +709,9 @@ class PlaybackService : MediaSessionService() {
|
||||||
|
|
||||||
mediaPlayer?.playMediaObject(playable, stream, true, true)
|
mediaPlayer?.playMediaObject(playable, stream, true, true)
|
||||||
recreateMediaSessionIfNeeded()
|
recreateMediaSessionIfNeeded()
|
||||||
updateNotificationAndMediaSession(playable)
|
// updateNotificationAndMediaSession(playable)
|
||||||
addPlayableToQueue(playable)
|
addPlayableToQueue(playable)
|
||||||
|
// EventBus.getDefault().post(PlaybackServiceEvent(PlaybackServiceEvent.Action.SERVICE_RESTARTED))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -732,7 +726,7 @@ class PlaybackService : MediaSessionService() {
|
||||||
fun notifyVideoSurfaceAbandoned() {
|
fun notifyVideoSurfaceAbandoned() {
|
||||||
mediaPlayer?.pause(true, false)
|
mediaPlayer?.pause(true, false)
|
||||||
mediaPlayer?.resetVideoSurface()
|
mediaPlayer?.resetVideoSurface()
|
||||||
updateNotificationAndMediaSession(playable)
|
// updateNotificationAndMediaSession(playable)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val taskManagerCallback: PSTMCallback = object : PSTMCallback {
|
private val taskManagerCallback: PSTMCallback = object : PSTMCallback {
|
||||||
|
@ -755,19 +749,19 @@ class PlaybackService : MediaSessionService() {
|
||||||
override fun statusChanged(newInfo: PSMPInfo?) {
|
override fun statusChanged(newInfo: PSMPInfo?) {
|
||||||
currentMediaType = mediaPlayer?.getCurrentMediaType() ?: MediaType.UNKNOWN
|
currentMediaType = mediaPlayer?.getCurrentMediaType() ?: MediaType.UNKNOWN
|
||||||
Log.d(TAG, "statusChanged called")
|
Log.d(TAG, "statusChanged called")
|
||||||
updateMediaSession(newInfo?.playerStatus)
|
// updateMediaSession(newInfo?.playerStatus)
|
||||||
if (newInfo != null) {
|
if (newInfo != null) {
|
||||||
when (newInfo.playerStatus) {
|
when (newInfo.playerStatus) {
|
||||||
PlayerStatus.INITIALIZED -> {
|
PlayerStatus.INITIALIZED -> {
|
||||||
if (mediaPlayer != null) writeMediaPlaying(mediaPlayer!!.pSMPInfo.playable, mediaPlayer!!.pSMPInfo.playerStatus, currentitem)
|
if (mediaPlayer != null) writeMediaPlaying(mediaPlayer!!.pSMPInfo.playable, mediaPlayer!!.pSMPInfo.playerStatus, currentitem)
|
||||||
updateNotificationAndMediaSession(newInfo.playable)
|
// updateNotificationAndMediaSession(newInfo.playable)
|
||||||
}
|
}
|
||||||
PlayerStatus.PREPARED -> {
|
PlayerStatus.PREPARED -> {
|
||||||
if (mediaPlayer != null) writeMediaPlaying(mediaPlayer!!.pSMPInfo.playable, mediaPlayer!!.pSMPInfo.playerStatus, currentitem)
|
if (mediaPlayer != null) writeMediaPlaying(mediaPlayer!!.pSMPInfo.playable, mediaPlayer!!.pSMPInfo.playerStatus, currentitem)
|
||||||
taskManager.startChapterLoader(newInfo.playable!!)
|
taskManager.startChapterLoader(newInfo.playable!!)
|
||||||
}
|
}
|
||||||
PlayerStatus.PAUSED -> {
|
PlayerStatus.PAUSED -> {
|
||||||
updateNotificationAndMediaSession(newInfo.playable)
|
// updateNotificationAndMediaSession(newInfo.playable)
|
||||||
cancelPositionObserver()
|
cancelPositionObserver()
|
||||||
if (mediaPlayer != null) writePlayerStatus(mediaPlayer!!.playerStatus)
|
if (mediaPlayer != null) writePlayerStatus(mediaPlayer!!.playerStatus)
|
||||||
}
|
}
|
||||||
|
@ -776,7 +770,7 @@ class PlaybackService : MediaSessionService() {
|
||||||
if (mediaPlayer != null) writePlayerStatus(mediaPlayer!!.playerStatus)
|
if (mediaPlayer != null) writePlayerStatus(mediaPlayer!!.playerStatus)
|
||||||
saveCurrentPosition(true, null, Playable.INVALID_TIME)
|
saveCurrentPosition(true, null, Playable.INVALID_TIME)
|
||||||
recreateMediaSessionIfNeeded()
|
recreateMediaSessionIfNeeded()
|
||||||
updateNotificationAndMediaSession(newInfo.playable)
|
// updateNotificationAndMediaSession(newInfo.playable)
|
||||||
setupPositionObserver()
|
setupPositionObserver()
|
||||||
// set sleep timer if auto-enabled
|
// set sleep timer if auto-enabled
|
||||||
var autoEnableByTime = true
|
var autoEnableByTime = true
|
||||||
|
@ -815,7 +809,7 @@ class PlaybackService : MediaSessionService() {
|
||||||
override fun onMediaChanged(reloadUI: Boolean) {
|
override fun onMediaChanged(reloadUI: Boolean) {
|
||||||
Log.d(TAG, "reloadUI callback reached")
|
Log.d(TAG, "reloadUI callback reached")
|
||||||
if (reloadUI) sendNotificationBroadcast(PlaybackServiceInterface.NOTIFICATION_TYPE_RELOAD, 0)
|
if (reloadUI) sendNotificationBroadcast(PlaybackServiceInterface.NOTIFICATION_TYPE_RELOAD, 0)
|
||||||
updateNotificationAndMediaSession(this@PlaybackService.playable)
|
// 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) {
|
||||||
|
@ -876,7 +870,7 @@ class PlaybackService : MediaSessionService() {
|
||||||
// Playable is being streamed and does not have a duration specified in the feed
|
// Playable is being streamed and does not have a duration specified in the feed
|
||||||
playable.setDuration(mediaPlayer!!.getDuration())
|
playable.setDuration(mediaPlayer!!.getDuration())
|
||||||
DBWriter.setFeedMedia(playable as FeedMedia?)
|
DBWriter.setFeedMedia(playable as FeedMedia?)
|
||||||
updateNotificationAndMediaSession(playable)
|
// updateNotificationAndMediaSession(playable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -924,12 +918,12 @@ class PlaybackService : MediaSessionService() {
|
||||||
if (!isFollowQueue) {
|
if (!isFollowQueue) {
|
||||||
Log.d(TAG, "getNextInQueue(), but follow queue is not enabled.")
|
Log.d(TAG, "getNextInQueue(), but follow queue is not enabled.")
|
||||||
writeMediaPlaying(nextItem.media, PlayerStatus.STOPPED, currentitem)
|
writeMediaPlaying(nextItem.media, PlayerStatus.STOPPED, currentitem)
|
||||||
updateNotificationAndMediaSession(nextItem.media)
|
// updateNotificationAndMediaSession(nextItem.media)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nextItem.media!!.localFileAvailable() && !isStreamingAllowed && isFollowQueue && nextItem.feed != null && !nextItem.feed!!.isLocalFeed) {
|
if (!nextItem.media!!.localFileAvailable() && !isStreamingAllowed && isFollowQueue && nextItem.feed != null && !nextItem.feed!!.isLocalFeed) {
|
||||||
// displayStreamingNotAllowedNotification(PlaybackServiceStarter(this, nextItem.media!!).intent)
|
displayStreamingNotAllowedNotification(PlaybackServiceStarter(this, nextItem.media!!).intent)
|
||||||
writeNoMediaPlaying()
|
writeNoMediaPlaying()
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -1081,62 +1075,62 @@ class PlaybackService : MediaSessionService() {
|
||||||
* @param playerStatus the current [PlayerStatus]
|
* @param playerStatus the current [PlayerStatus]
|
||||||
*/
|
*/
|
||||||
private fun updateMediaSession(playerStatus: PlayerStatus?) {
|
private fun updateMediaSession(playerStatus: PlayerStatus?) {
|
||||||
val sessionState = PlaybackStateCompat.Builder()
|
// val sessionState = PlaybackStateCompat.Builder()
|
||||||
val state = if (playerStatus != null) {
|
// val state = if (playerStatus != null) {
|
||||||
when (playerStatus) {
|
// when (playerStatus) {
|
||||||
PlayerStatus.PLAYING -> PlaybackStateCompat.STATE_PLAYING
|
// PlayerStatus.PLAYING -> PlaybackStateCompat.STATE_PLAYING
|
||||||
PlayerStatus.FALLBACK -> PlaybackStateCompat.STATE_PLAYING
|
// PlayerStatus.FALLBACK -> PlaybackStateCompat.STATE_PLAYING
|
||||||
PlayerStatus.PREPARED, PlayerStatus.PAUSED -> PlaybackStateCompat.STATE_PAUSED
|
// PlayerStatus.PREPARED, PlayerStatus.PAUSED -> PlaybackStateCompat.STATE_PAUSED
|
||||||
PlayerStatus.STOPPED -> PlaybackStateCompat.STATE_STOPPED
|
// PlayerStatus.STOPPED -> PlaybackStateCompat.STATE_STOPPED
|
||||||
PlayerStatus.SEEKING -> PlaybackStateCompat.STATE_FAST_FORWARDING
|
// PlayerStatus.SEEKING -> PlaybackStateCompat.STATE_FAST_FORWARDING
|
||||||
PlayerStatus.PREPARING, PlayerStatus.INITIALIZING -> PlaybackStateCompat.STATE_CONNECTING
|
// PlayerStatus.PREPARING, PlayerStatus.INITIALIZING -> PlaybackStateCompat.STATE_CONNECTING
|
||||||
PlayerStatus.ERROR -> PlaybackStateCompat.STATE_ERROR
|
// PlayerStatus.ERROR -> PlaybackStateCompat.STATE_ERROR
|
||||||
PlayerStatus.INITIALIZED, PlayerStatus.INDETERMINATE -> PlaybackStateCompat.STATE_NONE
|
// PlayerStatus.INITIALIZED, PlayerStatus.INDETERMINATE -> PlaybackStateCompat.STATE_NONE
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
PlaybackStateCompat.STATE_NONE
|
// PlaybackStateCompat.STATE_NONE
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
sessionState.setState(state, currentPosition.toLong(), currentPlaybackSpeed)
|
// sessionState.setState(state, currentPosition.toLong(), currentPlaybackSpeed)
|
||||||
val capabilities = (PlaybackStateCompat.ACTION_PLAY
|
// val capabilities = (PlaybackStateCompat.ACTION_PLAY
|
||||||
or PlaybackStateCompat.ACTION_PLAY_PAUSE
|
// or PlaybackStateCompat.ACTION_PLAY_PAUSE
|
||||||
or PlaybackStateCompat.ACTION_REWIND
|
// or PlaybackStateCompat.ACTION_REWIND
|
||||||
or PlaybackStateCompat.ACTION_PAUSE
|
// or PlaybackStateCompat.ACTION_PAUSE
|
||||||
or PlaybackStateCompat.ACTION_FAST_FORWARD
|
// or PlaybackStateCompat.ACTION_FAST_FORWARD
|
||||||
or PlaybackStateCompat.ACTION_SEEK_TO
|
// or PlaybackStateCompat.ACTION_SEEK_TO
|
||||||
or PlaybackStateCompat.ACTION_SET_PLAYBACK_SPEED)
|
// or PlaybackStateCompat.ACTION_SET_PLAYBACK_SPEED)
|
||||||
|
//
|
||||||
sessionState.setActions(capabilities)
|
// sessionState.setActions(capabilities)
|
||||||
|
|
||||||
// On Android Auto, custom actions are added in the following order around the play button, if no default
|
// 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
|
// 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)
|
// WearMediaSession.addWearExtrasToAction(rewindBuilder)
|
||||||
sessionState.addCustomAction(rewindBuilder.build())
|
//// 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)
|
// WearMediaSession.addWearExtrasToAction(fastForwardBuilder)
|
||||||
sessionState.addCustomAction(fastForwardBuilder.build())
|
// sessionState.addCustomAction(fastForwardBuilder.build())
|
||||||
|
|
||||||
if (showPlaybackSpeedOnFullNotification())
|
// if (showPlaybackSpeedOnFullNotification())
|
||||||
sessionState.addCustomAction(PlaybackStateCompat.CustomAction.Builder(CUSTOM_ACTION_CHANGE_PLAYBACK_SPEED,
|
// sessionState.addCustomAction(PlaybackStateCompat.CustomAction.Builder(CUSTOM_ACTION_CHANGE_PLAYBACK_SPEED,
|
||||||
getString(R.string.playback_speed), R.drawable.ic_notification_playback_speed).build())
|
// getString(R.string.playback_speed), R.drawable.ic_notification_playback_speed).build())
|
||||||
|
|
||||||
if (showNextChapterOnFullNotification()) {
|
// if (showNextChapterOnFullNotification()) {
|
||||||
if (!playable?.getChapters().isNullOrEmpty())
|
// if (!playable?.getChapters().isNullOrEmpty())
|
||||||
sessionState.addCustomAction(PlaybackStateCompat.CustomAction.Builder(CUSTOM_ACTION_NEXT_CHAPTER,
|
// sessionState.addCustomAction(PlaybackStateCompat.CustomAction.Builder(CUSTOM_ACTION_NEXT_CHAPTER,
|
||||||
getString(R.string.next_chapter), R.drawable.ic_notification_next_chapter).build())
|
// getString(R.string.next_chapter), R.drawable.ic_notification_next_chapter).build())
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (showSkipOnFullNotification())
|
// if (showSkipOnFullNotification())
|
||||||
sessionState.addCustomAction(PlaybackStateCompat.CustomAction.Builder(CUSTOM_ACTION_SKIP_TO_NEXT,
|
// sessionState.addCustomAction(PlaybackStateCompat.CustomAction.Builder(CUSTOM_ACTION_SKIP_TO_NEXT,
|
||||||
getString(R.string.skip_episode_label), R.drawable.ic_notification_skip).build())
|
// getString(R.string.skip_episode_label), R.drawable.ic_notification_skip).build())
|
||||||
|
|
||||||
|
|
||||||
if (mediaSession != null) {
|
// if (mediaSession != null) {
|
||||||
WearMediaSession.mediaSessionSetExtraForWear(mediaSession!!)
|
// WearMediaSession.mediaSessionSetExtraForWear(mediaSession!!)
|
||||||
// mediaSession!!.setPlaybackState(sessionState.build())
|
//// mediaSession!!.setPlaybackState(sessionState.build())
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateNotificationAndMediaSession(p: Playable?) {
|
private fun updateNotificationAndMediaSession(p: Playable?) {
|
||||||
|
@ -1147,25 +1141,27 @@ class PlaybackService : MediaSessionService() {
|
||||||
private fun updateMediaSessionMetadata(p: Playable?) {
|
private fun updateMediaSessionMetadata(p: Playable?) {
|
||||||
if (p == null || mediaSession == null) return
|
if (p == null || mediaSession == null) return
|
||||||
|
|
||||||
// TODO: what's this?
|
// TODO: how to set meta data
|
||||||
// val builder = MediaMetadataCompat.Builder()
|
// val builder = MediaMetadata.Builder()
|
||||||
// builder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, p.getFeedTitle())
|
// builder.setArtist(p.getFeedTitle())
|
||||||
// builder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, p.getEpisodeTitle())
|
// builder.setTitle(p.getEpisodeTitle())
|
||||||
// builder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, p.getFeedTitle())
|
// builder.setAlbumArtist(p.getFeedTitle())
|
||||||
// builder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, p.getDuration().toLong())
|
//// builder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, p.getDuration().toLong())
|
||||||
// builder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, p.getEpisodeTitle())
|
// builder.setDisplayTitle(p.getEpisodeTitle())
|
||||||
// builder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, p.getFeedTitle())
|
// builder.setSubtitle(p.getFeedTitle())
|
||||||
|
|
||||||
// TODO: what's this?
|
// TODO: what's this?
|
||||||
// mediaSession!!.setSessionActivity(PendingIntent.getActivity(this, R.id.pending_intent_player_activity,
|
// mediaSession!!.setSessionActivity(PendingIntent.getActivity(this, R.id.pending_intent_player_activity,
|
||||||
// getPlayerActivityIntent(this), FLAG_IMMUTABLE))
|
// getPlayerActivityIntent(this), FLAG_IMMUTABLE))
|
||||||
|
|
||||||
// try {
|
// try {
|
||||||
// mediaSession!!.setMetadata(builder.build())
|
//// mediaSession!!.setMetadata(builder.build())
|
||||||
|
// val mediaItem = MediaItem.Builder().setMediaMetadata(builder.build()).build()
|
||||||
// } catch (e: OutOfMemoryError) {
|
// } catch (e: OutOfMemoryError) {
|
||||||
// Log.e(TAG, "Setting media session metadata", e)
|
// Log.e(TAG, "Setting media session metadata", e)
|
||||||
// builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, null)
|
// builder.setArtworkUri(null)
|
||||||
// mediaSession!!.setMetadata(builder.build())
|
//// mediaSession!!.setMetadata(builder.build())
|
||||||
|
// val mediaItem = MediaItem.Builder().setMediaMetadata(builder.build()).build()
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1178,16 +1174,16 @@ class PlaybackService : MediaSessionService() {
|
||||||
* Prepares notification and starts the service in the foreground.
|
* Prepares notification and starts the service in the foreground.
|
||||||
*/
|
*/
|
||||||
// TODO: not needed?
|
// TODO: not needed?
|
||||||
@Synchronized
|
// @Synchronized
|
||||||
private fun setupNotification(playable: Playable?) {
|
// private fun setupNotification(playable: Playable?) {
|
||||||
Log.d(TAG, "setupNotification")
|
// Log.d(TAG, "setupNotification")
|
||||||
playableIconLoaderThread?.interrupt()
|
// playableIconLoaderThread?.interrupt()
|
||||||
|
//
|
||||||
if (playable == null || mediaPlayer == null) {
|
// if (playable == null || mediaPlayer == null) {
|
||||||
Log.d(TAG, "setupNotification: playable=$playable mediaPlayer=$mediaPlayer")
|
// Log.d(TAG, "setupNotification: playable=$playable mediaPlayer=$mediaPlayer")
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Persists the current position and last played time of the media file.
|
* Persists the current position and last played time of the media file.
|
||||||
|
@ -1362,14 +1358,12 @@ class PlaybackService : MediaSessionService() {
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun speedPresetChanged(event: SpeedPresetChangedEvent) {
|
fun onSpeedPresetChanged(event: SpeedPresetChangedEvent) {
|
||||||
val item = (playable as? FeedMedia)?.item ?: currentitem
|
val item = (playable as? FeedMedia)?.item ?: currentitem
|
||||||
// if (playable is FeedMedia) {
|
|
||||||
if (item?.feed?.id == event.feedId) {
|
if (item?.feed?.id == event.feedId) {
|
||||||
if (event.speed == FeedPreferences.SPEED_USE_GLOBAL) setSpeed(getPlaybackSpeed(playable!!.getMediaType()))
|
if (event.speed == FeedPreferences.SPEED_USE_GLOBAL) setSpeed(getPlaybackSpeed(playable!!.getMediaType()))
|
||||||
else setSpeed(event.speed)
|
else setSpeed(event.speed)
|
||||||
}
|
}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
|
@ -1394,7 +1388,6 @@ class PlaybackService : MediaSessionService() {
|
||||||
currentitem = event.item
|
currentitem = event.item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun resume() {
|
fun resume() {
|
||||||
mediaPlayer?.resume()
|
mediaPlayer?.resume()
|
||||||
taskManager.restartSleepTimer()
|
taskManager.restartSleepTimer()
|
||||||
|
|
|
@ -14,9 +14,9 @@ class VisitWebsiteActionButton(item: FeedItem) : ItemActionButton(item) {
|
||||||
return R.drawable.ic_web
|
return R.drawable.ic_web
|
||||||
}
|
}
|
||||||
override fun onClick(context: Context) {
|
override fun onClick(context: Context) {
|
||||||
if (item.link!= null) openInBrowser(context, item.link!!)
|
if (!item.link.isNullOrEmpty()) openInBrowser(context, item.link!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val visibility: Int
|
override val visibility: Int
|
||||||
get() = if (item.link == null) View.INVISIBLE else View.VISIBLE
|
get() = if (item.link.isNullOrEmpty()) View.INVISIBLE else View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,6 @@ import ac.mdiq.podcini.ui.dialog.MediaPlayerErrorDialog
|
||||||
import ac.mdiq.podcini.ui.dialog.SkipPreferenceDialog
|
import ac.mdiq.podcini.ui.dialog.SkipPreferenceDialog
|
||||||
import ac.mdiq.podcini.ui.dialog.SleepTimerDialog
|
import ac.mdiq.podcini.ui.dialog.SleepTimerDialog
|
||||||
import ac.mdiq.podcini.ui.dialog.VariableSpeedDialog
|
import ac.mdiq.podcini.ui.dialog.VariableSpeedDialog
|
||||||
import ac.mdiq.podcini.ui.fragment.EpisodeHomeFragment.Companion.fetchHtmlSource
|
|
||||||
import ac.mdiq.podcini.ui.utils.ShownotesCleaner
|
|
||||||
import ac.mdiq.podcini.ui.view.ChapterSeekBar
|
import ac.mdiq.podcini.ui.view.ChapterSeekBar
|
||||||
import ac.mdiq.podcini.ui.view.PlayButton
|
import ac.mdiq.podcini.ui.view.PlayButton
|
||||||
import ac.mdiq.podcini.ui.view.PlaybackSpeedIndicatorView
|
import ac.mdiq.podcini.ui.view.PlaybackSpeedIndicatorView
|
||||||
|
@ -65,8 +63,6 @@ import io.reactivex.MaybeEmitter
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import net.dankito.readability4j.Readability4J
|
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.greenrobot.eventbus.Subscribe
|
import org.greenrobot.eventbus.Subscribe
|
||||||
import org.greenrobot.eventbus.ThreadMode
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
|
@ -75,6 +71,7 @@ import java.text.NumberFormat
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the audio player.
|
* Shows the audio player.
|
||||||
*/
|
*/
|
||||||
|
@ -192,7 +189,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
||||||
val theMedia = controller?.getMedia() ?: return
|
val theMedia = controller?.getMedia() ?: return
|
||||||
Log.d(TAG, "loadMediaInfo $theMedia")
|
Log.d(TAG, "loadMediaInfo $theMedia")
|
||||||
|
|
||||||
if (currentMedia == null || theMedia?.getIdentifier() != currentMedia?.getIdentifier()) {
|
if (currentMedia == null || theMedia.getIdentifier() != currentMedia?.getIdentifier()) {
|
||||||
Log.d(TAG, "loadMediaInfo loading details")
|
Log.d(TAG, "loadMediaInfo loading details")
|
||||||
disposable?.dispose()
|
disposable?.dispose()
|
||||||
disposable = Maybe.create<Playable> { emitter: MaybeEmitter<Playable?> ->
|
disposable = Maybe.create<Playable> { emitter: MaybeEmitter<Playable?> ->
|
||||||
|
@ -297,6 +294,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
||||||
currentitem = event.item
|
currentitem = event.item
|
||||||
if (currentMedia?.getIdentifier() == null || currentitem!!.media!!.getIdentifier() != currentMedia?.getIdentifier())
|
if (currentMedia?.getIdentifier() == null || currentitem!!.media!!.getIdentifier() != currentMedia?.getIdentifier())
|
||||||
itemDescFrag.setItem(currentitem!!)
|
itemDescFrag.setItem(currentitem!!)
|
||||||
|
(activity as MainActivity).setPlayerVisible(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||||
|
@ -404,8 +402,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.share_notes -> {
|
R.id.share_notes -> {
|
||||||
if (feedItem == null) return false
|
val notes = if (itemDescFrag.showHomeText) itemDescFrag.readerhtml else feedItem?.description
|
||||||
val notes = feedItem.description
|
|
||||||
if (!notes.isNullOrEmpty()) {
|
if (!notes.isNullOrEmpty()) {
|
||||||
val shareText = if (Build.VERSION.SDK_INT >= 24) Html.fromHtml(notes, Html.FROM_HTML_MODE_LEGACY).toString()
|
val shareText = if (Build.VERSION.SDK_INT >= 24) Html.fromHtml(notes, Html.FROM_HTML_MODE_LEGACY).toString()
|
||||||
else Html.fromHtml(notes).toString()
|
else Html.fromHtml(notes).toString()
|
||||||
|
@ -649,6 +646,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
||||||
when (event.action) {
|
when (event.action) {
|
||||||
PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN -> (activity as MainActivity).setPlayerVisible(false)
|
PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN -> (activity as MainActivity).setPlayerVisible(false)
|
||||||
PlaybackServiceEvent.Action.SERVICE_STARTED -> (activity as MainActivity).setPlayerVisible(true)
|
PlaybackServiceEvent.Action.SERVICE_STARTED -> (activity as MainActivity).setPlayerVisible(true)
|
||||||
|
// PlaybackServiceEvent.Action.SERVICE_RESTARTED -> (activity as MainActivity).setPlayerVisible(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,14 @@ import ac.mdiq.podcini.R
|
||||||
import ac.mdiq.podcini.databinding.EpisodeHomeFragmentBinding
|
import ac.mdiq.podcini.databinding.EpisodeHomeFragmentBinding
|
||||||
import ac.mdiq.podcini.storage.model.feed.FeedItem
|
import ac.mdiq.podcini.storage.model.feed.FeedItem
|
||||||
import ac.mdiq.podcini.ui.utils.ShownotesCleaner
|
import ac.mdiq.podcini.ui.utils.ShownotesCleaner
|
||||||
import android.speech.tts.TextToSpeech
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.speech.tts.TextToSpeech
|
||||||
import android.text.Html
|
import android.text.Html
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.*
|
import android.view.*
|
||||||
|
import android.webkit.WebView
|
||||||
|
import android.webkit.WebViewClient
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
@ -37,22 +39,18 @@ class EpisodeHomeFragment : Fragment(), Toolbar.OnMenuItemClickListener, TextToS
|
||||||
|
|
||||||
// private var item: FeedItem? = null
|
// private var item: FeedItem? = null
|
||||||
|
|
||||||
private lateinit var tts: TextToSpeech
|
private var startIndex = 0
|
||||||
|
private var tts: TextToSpeech? = null
|
||||||
|
private var ttsSpeed = 1.0f
|
||||||
|
|
||||||
private lateinit var toolbar: MaterialToolbar
|
private lateinit var toolbar: MaterialToolbar
|
||||||
|
|
||||||
private var disposable: Disposable? = null
|
private var disposable: Disposable? = null
|
||||||
|
|
||||||
// private var readerhtml: String? = null
|
private var readerhtml: String? = null
|
||||||
private var readMode = false
|
private var readMode = false
|
||||||
private var ttsPlaying = false
|
private var ttsPlaying = false
|
||||||
|
private var jsEnabled = 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 {
|
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
super.onCreateView(inflater, container, savedInstanceState)
|
super.onCreateView(inflater, container, savedInstanceState)
|
||||||
|
@ -66,7 +64,22 @@ class EpisodeHomeFragment : Fragment(), Toolbar.OnMenuItemClickListener, TextToS
|
||||||
toolbar.setNavigationOnClickListener { parentFragmentManager.popBackStack() }
|
toolbar.setNavigationOnClickListener { parentFragmentManager.popBackStack() }
|
||||||
toolbar.setOnMenuItemClickListener(this)
|
toolbar.setOnMenuItemClickListener(this)
|
||||||
|
|
||||||
if (currentItem?.link != null) showContent()
|
if (!currentItem?.link.isNullOrEmpty()) showContent()
|
||||||
|
else {
|
||||||
|
Toast.makeText(context, R.string.web_content_not_available, Toast.LENGTH_LONG).show()
|
||||||
|
parentFragmentManager.popBackStack()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.webView.apply {
|
||||||
|
webViewClient = object : WebViewClient() {
|
||||||
|
override fun onPageFinished(view: WebView?, url: String?) {
|
||||||
|
val isEmpty = view?.title.isNullOrEmpty() && view?.contentDescription.isNullOrEmpty()
|
||||||
|
if (isEmpty) {
|
||||||
|
Log.d(TAG, "content is empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updateAppearance()
|
updateAppearance()
|
||||||
return binding.root
|
return binding.root
|
||||||
|
@ -80,34 +93,31 @@ class EpisodeHomeFragment : Fragment(), Toolbar.OnMenuItemClickListener, TextToS
|
||||||
|
|
||||||
override fun onInit(status: Int) {
|
override fun onInit(status: Int) {
|
||||||
if (status == TextToSpeech.SUCCESS) {
|
if (status == TextToSpeech.SUCCESS) {
|
||||||
// TTS initialization successful
|
|
||||||
Log.i(TAG, "TTS init success with Locale: ${currentItem?.feed?.language}")
|
Log.i(TAG, "TTS init success with Locale: ${currentItem?.feed?.language}")
|
||||||
if (currentItem?.feed?.language != null) {
|
if (currentItem?.feed?.language != null) {
|
||||||
val result = tts.setLanguage(Locale(currentItem!!.feed!!.language!!))
|
val result = tts?.setLanguage(Locale(currentItem!!.feed!!.language!!))
|
||||||
// val result = tts.setLanguage(Locale.UK)
|
|
||||||
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
|
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
|
||||||
Log.w(TAG, "TTS language not supported")
|
Log.w(TAG, "TTS language not supported ${currentItem?.feed?.language}")
|
||||||
// Language not supported
|
Toast.makeText(context, R.string.language_not_supported_by_tts, Toast.LENGTH_LONG).show()
|
||||||
// Handle the error or fallback to default behavior
|
|
||||||
}
|
}
|
||||||
|
ttsSpeed = currentItem?.feed?.preferences?.feedPlaybackSpeed ?: 1.0f
|
||||||
|
tts?.setSpeechRate(ttsSpeed)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TTS initialization failed
|
|
||||||
// Handle the error or fallback to default behavior
|
|
||||||
Log.w(TAG, "TTS init failed")
|
Log.w(TAG, "TTS init failed")
|
||||||
|
Toast.makeText(context, R.string.tts_init_failed, Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showContent() {
|
private fun showReaderContent() {
|
||||||
if (readMode) {
|
if (!currentItem?.link.isNullOrEmpty()) {
|
||||||
var readerhtml: String? = null
|
|
||||||
if (cleanedNotes == null) {
|
if (cleanedNotes == null) {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val url = currentItem!!.link!!
|
val url = currentItem!!.link!!
|
||||||
val htmlSource = fetchHtmlSource(url)
|
val htmlSource = fetchHtmlSource(url)
|
||||||
val readability4J = Readability4J(currentItem?.link!!, htmlSource)
|
val readability4J = Readability4J(currentItem?.link!!, htmlSource)
|
||||||
val article = readability4J.parse()
|
val article = readability4J.parse()
|
||||||
textContent = article.textContent
|
readerText = article.textContent
|
||||||
// Log.d(TAG, "readability4J: ${article.textContent}")
|
// Log.d(TAG, "readability4J: ${article.textContent}")
|
||||||
readerhtml = article.contentWithDocumentsCharsetOrUtf8
|
readerhtml = article.contentWithDocumentsCharsetOrUtf8
|
||||||
if (!readerhtml.isNullOrEmpty()) {
|
if (!readerhtml.isNullOrEmpty()) {
|
||||||
|
@ -116,19 +126,28 @@ class EpisodeHomeFragment : Fragment(), Toolbar.OnMenuItemClickListener, TextToS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (!cleanedNotes.isNullOrEmpty()) {
|
if (!cleanedNotes.isNullOrEmpty()) {
|
||||||
|
if (tts == null) tts = TextToSpeech(requireContext(), this)
|
||||||
binding.readerView.loadDataWithBaseURL("https://127.0.0.1", cleanedNotes!!, "text/html", "UTF-8", null)
|
binding.readerView.loadDataWithBaseURL("https://127.0.0.1", cleanedNotes!!, "text/html", "UTF-8", null)
|
||||||
// binding.readerView.loadDataWithBaseURL(currentItem!!.link!!, readerhtml!!, "text/html", "UTF-8", null)
|
|
||||||
binding.readerView.visibility = View.VISIBLE
|
binding.readerView.visibility = View.VISIBLE
|
||||||
binding.webView.visibility = View.GONE
|
binding.webView.visibility = View.GONE
|
||||||
} else Toast.makeText(context, R.string.web_content_not_available, Toast.LENGTH_LONG).show()
|
} else Toast.makeText(context, R.string.web_content_not_available, Toast.LENGTH_LONG).show()
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
private fun showWebContent() {
|
||||||
if (!currentItem?.link.isNullOrEmpty()) {
|
if (!currentItem?.link.isNullOrEmpty()) {
|
||||||
|
binding.webView.settings.javaScriptEnabled = jsEnabled
|
||||||
|
Log.d(TAG, "currentItem!!.link ${currentItem!!.link}")
|
||||||
binding.webView.loadUrl(currentItem!!.link!!)
|
binding.webView.loadUrl(currentItem!!.link!!)
|
||||||
binding.readerView.visibility = View.GONE
|
binding.readerView.visibility = View.GONE
|
||||||
binding.webView.visibility = View.VISIBLE
|
binding.webView.visibility = View.VISIBLE
|
||||||
} else Toast.makeText(context, R.string.web_content_not_available, Toast.LENGTH_LONG).show()
|
} else Toast.makeText(context, R.string.web_content_not_available, Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showContent() {
|
||||||
|
if (readMode) showReaderContent()
|
||||||
|
else showWebContent()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("Deprecated in Java")
|
@Deprecated("Deprecated in Java")
|
||||||
|
@ -138,6 +157,7 @@ class EpisodeHomeFragment : Fragment(), Toolbar.OnMenuItemClickListener, TextToS
|
||||||
if (readMode) {
|
if (readMode) {
|
||||||
if (ttsPlaying) textSpeech.setIcon(R.drawable.ic_pause) else textSpeech.setIcon(R.drawable.ic_play_24dp)
|
if (ttsPlaying) textSpeech.setIcon(R.drawable.ic_pause) else textSpeech.setIcon(R.drawable.ic_play_24dp)
|
||||||
}
|
}
|
||||||
|
menu.findItem(R.id.share_notes).setVisible(readMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@UnstableApi override fun onMenuItemClick(menuItem: MenuItem): Boolean {
|
@UnstableApi override fun onMenuItemClick(menuItem: MenuItem): Boolean {
|
||||||
|
@ -147,31 +167,35 @@ class EpisodeHomeFragment : Fragment(), Toolbar.OnMenuItemClickListener, TextToS
|
||||||
switchMode()
|
switchMode()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
R.id.switchJS -> {
|
||||||
|
Log.d(TAG, "switchJS selected")
|
||||||
|
jsEnabled = !jsEnabled
|
||||||
|
showWebContent()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
R.id.text_speech -> {
|
R.id.text_speech -> {
|
||||||
Log.d(TAG, "text_speech selected: $textContent")
|
Log.d(TAG, "text_speech selected: $readerText")
|
||||||
if (tts.isSpeaking) tts.stop()
|
if (tts != null) {
|
||||||
|
if (tts!!.isSpeaking) tts?.stop()
|
||||||
if (!ttsPlaying) {
|
if (!ttsPlaying) {
|
||||||
ttsPlaying = true
|
ttsPlaying = true
|
||||||
if (textContent != null) {
|
if (!readerText.isNullOrEmpty()) {
|
||||||
val maxTextLength = 4000
|
tts?.setSpeechRate(ttsSpeed)
|
||||||
var startIndex = 0
|
while (startIndex < readerText!!.length) {
|
||||||
var endIndex = minOf(maxTextLength, textContent!!.length)
|
val endIndex = minOf(startIndex + maxChunkLength, readerText!!.length)
|
||||||
while (startIndex < textContent!!.length) {
|
val chunk = readerText!!.substring(startIndex, endIndex)
|
||||||
val chunk = textContent!!.substring(startIndex, endIndex)
|
tts?.speak(chunk, TextToSpeech.QUEUE_ADD, null, null)
|
||||||
tts.speak(chunk, TextToSpeech.QUEUE_ADD, null, null)
|
startIndex += maxChunkLength
|
||||||
|
|
||||||
startIndex += maxTextLength
|
|
||||||
endIndex = minOf(endIndex + maxTextLength, textContent!!.length)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else ttsPlaying = false
|
} else ttsPlaying = false
|
||||||
|
|
||||||
updateAppearance()
|
updateAppearance()
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.share_notes -> {
|
R.id.share_notes -> {
|
||||||
if (currentItem == null) return false
|
val notes = readerhtml
|
||||||
val notes = currentItem!!.description
|
|
||||||
if (!notes.isNullOrEmpty()) {
|
if (!notes.isNullOrEmpty()) {
|
||||||
val shareText = if (Build.VERSION.SDK_INT >= 24) Html.fromHtml(notes, Html.FROM_HTML_MODE_LEGACY).toString()
|
val shareText = if (Build.VERSION.SDK_INT >= 24) Html.fromHtml(notes, Html.FROM_HTML_MODE_LEGACY).toString()
|
||||||
else Html.fromHtml(notes).toString()
|
else Html.fromHtml(notes).toString()
|
||||||
|
@ -201,7 +225,7 @@ class EpisodeHomeFragment : Fragment(), Toolbar.OnMenuItemClickListener, TextToS
|
||||||
Log.d(TAG, "onDestroyView")
|
Log.d(TAG, "onDestroyView")
|
||||||
_binding = null
|
_binding = null
|
||||||
disposable?.dispose()
|
disposable?.dispose()
|
||||||
tts.shutdown()
|
tts?.shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
@UnstableApi private fun updateAppearance() {
|
@UnstableApi private fun updateAppearance() {
|
||||||
|
@ -214,12 +238,14 @@ class EpisodeHomeFragment : Fragment(), Toolbar.OnMenuItemClickListener, TextToS
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "EpisodeWebviewFragment"
|
private const val TAG = "EpisodeHomeFragment"
|
||||||
private const val ARG_FEEDITEM = "feeditem"
|
private const val ARG_FEEDITEM = "feeditem"
|
||||||
|
|
||||||
private var textContent: String? = null
|
const val maxChunkLength = 200
|
||||||
|
|
||||||
|
private var readerText: String? = null
|
||||||
private var cleanedNotes: String? = null
|
private var cleanedNotes: String? = null
|
||||||
private var currentItem: FeedItem? = null
|
var currentItem: FeedItem? = null
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun newInstance(item: FeedItem): EpisodeHomeFragment {
|
fun newInstance(item: FeedItem): EpisodeHomeFragment {
|
||||||
|
@ -229,7 +255,9 @@ class EpisodeHomeFragment : Fragment(), Toolbar.OnMenuItemClickListener, TextToS
|
||||||
if (item.itemIdentifier != currentItem?.itemIdentifier) {
|
if (item.itemIdentifier != currentItem?.itemIdentifier) {
|
||||||
currentItem = item
|
currentItem = item
|
||||||
cleanedNotes = null
|
cleanedNotes = null
|
||||||
textContent = null
|
readerText = null
|
||||||
|
} else {
|
||||||
|
currentItem?.feed = item.feed
|
||||||
}
|
}
|
||||||
// args.putSerializable(ARG_FEEDITEM, item)
|
// args.putSerializable(ARG_FEEDITEM, item)
|
||||||
// fragment.arguments = args
|
// fragment.arguments = args
|
||||||
|
|
|
@ -17,6 +17,7 @@ import ac.mdiq.podcini.ui.view.CircularProgressBar
|
||||||
import ac.mdiq.podcini.ui.utils.ThemeUtils
|
import ac.mdiq.podcini.ui.utils.ThemeUtils
|
||||||
import ac.mdiq.podcini.ui.utils.ShownotesCleaner
|
import ac.mdiq.podcini.ui.utils.ShownotesCleaner
|
||||||
import ac.mdiq.podcini.ui.actions.menuhandler.FeedItemMenuHandler
|
import ac.mdiq.podcini.ui.actions.menuhandler.FeedItemMenuHandler
|
||||||
|
import ac.mdiq.podcini.ui.fragment.EpisodeHomeFragment.Companion.currentItem
|
||||||
import ac.mdiq.podcini.ui.view.ShownotesWebView
|
import ac.mdiq.podcini.ui.view.ShownotesWebView
|
||||||
import ac.mdiq.podcini.util.Converter
|
import ac.mdiq.podcini.util.Converter
|
||||||
import ac.mdiq.podcini.util.DateFormatter
|
import ac.mdiq.podcini.util.DateFormatter
|
||||||
|
@ -25,6 +26,7 @@ import ac.mdiq.podcini.util.event.EpisodeDownloadEvent
|
||||||
import ac.mdiq.podcini.util.event.FeedItemEvent
|
import ac.mdiq.podcini.util.event.FeedItemEvent
|
||||||
import ac.mdiq.podcini.util.event.PlayerStatusEvent
|
import ac.mdiq.podcini.util.event.PlayerStatusEvent
|
||||||
import ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent
|
import ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent
|
||||||
|
import ac.mdiq.podcini.util.event.settings.SpeedPresetChangedEvent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Html
|
import android.text.Html
|
||||||
|
@ -71,6 +73,8 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||||
private var _binding: EpisodeInfoFragmentBinding? = null
|
private var _binding: EpisodeInfoFragmentBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
private var homeFragment: EpisodeHomeFragment? = null
|
||||||
|
|
||||||
private var itemsLoaded = false
|
private var itemsLoaded = false
|
||||||
private var item: FeedItem? = null
|
private var item: FeedItem? = null
|
||||||
private var webviewData: String? = null
|
private var webviewData: String? = null
|
||||||
|
@ -141,7 +145,8 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||||
noMediaLabel = binding.noMediaLabel
|
noMediaLabel = binding.noMediaLabel
|
||||||
|
|
||||||
butAction0.setOnClickListener {
|
butAction0.setOnClickListener {
|
||||||
if (item?.link != null) (activity as MainActivity).loadChildFragment(EpisodeHomeFragment.newInstance(item!!))
|
homeFragment = EpisodeHomeFragment.newInstance(item!!)
|
||||||
|
if (item?.link != null) (activity as MainActivity).loadChildFragment(homeFragment!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
butAction1.setOnClickListener(View.OnClickListener {
|
butAction1.setOnClickListener(View.OnClickListener {
|
||||||
|
@ -398,7 +403,6 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||||
|
|
||||||
@UnstableApi private fun load() {
|
@UnstableApi private fun load() {
|
||||||
disposable?.dispose()
|
disposable?.dispose()
|
||||||
|
|
||||||
if (!itemsLoaded) progbarLoading.visibility = View.VISIBLE
|
if (!itemsLoaded) progbarLoading.visibility = View.VISIBLE
|
||||||
|
|
||||||
disposable = Observable.fromCallable<FeedItem?> { this.loadInBackground() }
|
disposable = Observable.fromCallable<FeedItem?> { this.loadInBackground() }
|
||||||
|
|
|
@ -64,7 +64,7 @@ import org.greenrobot.eventbus.ThreadMode
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
class PlayerDetailsFragment : Fragment() {
|
class PlayerDetailsFragment : Fragment() {
|
||||||
private lateinit var webvDescription: ShownotesWebView
|
private lateinit var shownoteView: ShownotesWebView
|
||||||
|
|
||||||
private var _binding: PlayerDetailsFragmentBinding? = null
|
private var _binding: PlayerDetailsFragmentBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
@ -79,8 +79,9 @@ class PlayerDetailsFragment : Fragment() {
|
||||||
private var webViewLoader: Disposable? = null
|
private var webViewLoader: Disposable? = null
|
||||||
private var controller: PlaybackController? = null
|
private var controller: PlaybackController? = null
|
||||||
|
|
||||||
private var showHomeText = false
|
internal var showHomeText = false
|
||||||
var homeText: String? = null
|
internal var homeText: String? = null
|
||||||
|
internal var readerhtml: String? = null
|
||||||
|
|
||||||
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
Log.d(TAG, "fragment onCreateView")
|
Log.d(TAG, "fragment onCreateView")
|
||||||
|
@ -96,20 +97,20 @@ class PlayerDetailsFragment : Fragment() {
|
||||||
binding.butNextChapter.setOnClickListener { seekToNextChapter() }
|
binding.butNextChapter.setOnClickListener { seekToNextChapter() }
|
||||||
|
|
||||||
Log.d(TAG, "fragment onCreateView")
|
Log.d(TAG, "fragment onCreateView")
|
||||||
webvDescription = binding.webview
|
shownoteView = binding.webview
|
||||||
webvDescription.setTimecodeSelectedListener { time: Int? -> controller?.seekTo(time!!) }
|
shownoteView.setTimecodeSelectedListener { time: Int? -> controller?.seekTo(time!!) }
|
||||||
webvDescription.setPageFinishedListener {
|
shownoteView.setPageFinishedListener {
|
||||||
// Restoring the scroll position might not always work
|
// Restoring the scroll position might not always work
|
||||||
webvDescription.postDelayed({ this@PlayerDetailsFragment.restoreFromPreference() }, 50)
|
shownoteView.postDelayed({ this@PlayerDetailsFragment.restoreFromPreference() }, 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.root.addOnLayoutChangeListener(object : OnLayoutChangeListener {
|
binding.root.addOnLayoutChangeListener(object : OnLayoutChangeListener {
|
||||||
override fun onLayoutChange(v: View, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) {
|
override fun onLayoutChange(v: View, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) {
|
||||||
if (binding.root.measuredHeight != webvDescription.minimumHeight) webvDescription.setMinimumHeight(binding.root.measuredHeight)
|
if (binding.root.measuredHeight != shownoteView.minimumHeight) shownoteView.setMinimumHeight(binding.root.measuredHeight)
|
||||||
binding.root.removeOnLayoutChangeListener(this)
|
binding.root.removeOnLayoutChangeListener(this)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
registerForContextMenu(webvDescription)
|
registerForContextMenu(shownoteView)
|
||||||
controller = object : PlaybackController(requireActivity()) {
|
controller = object : PlaybackController(requireActivity()) {
|
||||||
override fun loadMediaInfo() {
|
override fun loadMediaInfo() {
|
||||||
load()
|
load()
|
||||||
|
@ -125,12 +126,12 @@ class PlayerDetailsFragment : Fragment() {
|
||||||
controller?.release()
|
controller?.release()
|
||||||
controller = null
|
controller = null
|
||||||
Log.d(TAG, "Fragment destroyed")
|
Log.d(TAG, "Fragment destroyed")
|
||||||
webvDescription.removeAllViews()
|
shownoteView.removeAllViews()
|
||||||
webvDescription.destroy()
|
shownoteView.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onContextItemSelected(item: MenuItem): Boolean {
|
override fun onContextItemSelected(item: MenuItem): Boolean {
|
||||||
return webvDescription.onContextItemSelected(item)
|
return shownoteView.onContextItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
@UnstableApi private fun load() {
|
@UnstableApi private fun load() {
|
||||||
|
@ -168,7 +169,7 @@ class PlayerDetailsFragment : Fragment() {
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe({ data: String? ->
|
.subscribe({ data: String? ->
|
||||||
webvDescription.loadDataWithBaseURL("https://127.0.0.1", data!!, "text/html", "utf-8", "about:blank")
|
shownoteView.loadDataWithBaseURL("https://127.0.0.1", data!!, "text/html", "utf-8", "about:blank")
|
||||||
Log.d(TAG, "Webview loaded")
|
Log.d(TAG, "Webview loaded")
|
||||||
}, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
|
}, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
|
||||||
loadMediaInfo()
|
loadMediaInfo()
|
||||||
|
@ -198,20 +199,20 @@ class PlayerDetailsFragment : Fragment() {
|
||||||
val htmlSource = fetchHtmlSource(url)
|
val htmlSource = fetchHtmlSource(url)
|
||||||
val readability4J = Readability4J(item!!.link!!, htmlSource)
|
val readability4J = Readability4J(item!!.link!!, htmlSource)
|
||||||
val article = readability4J.parse()
|
val article = readability4J.parse()
|
||||||
val readerhtml = article.contentWithDocumentsCharsetOrUtf8
|
readerhtml = article.contentWithDocumentsCharsetOrUtf8
|
||||||
if (readerhtml != null) {
|
if (!readerhtml.isNullOrEmpty()) {
|
||||||
val shownotesCleaner = ShownotesCleaner(requireContext(), readerhtml, 0)
|
val shownotesCleaner = ShownotesCleaner(requireContext(), readerhtml!!, 0)
|
||||||
homeText = shownotesCleaner.processShownotes()
|
homeText = shownotesCleaner.processShownotes()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!homeText.isNullOrEmpty())
|
if (!homeText.isNullOrEmpty())
|
||||||
binding.webview.loadDataWithBaseURL("https://127.0.0.1", homeText!!, "text/html", "UTF-8", null)
|
shownoteView.loadDataWithBaseURL("https://127.0.0.1", homeText!!, "text/html", "UTF-8", null)
|
||||||
else Toast.makeText(context, R.string.web_content_not_available, Toast.LENGTH_LONG).show()
|
else Toast.makeText(context, R.string.web_content_not_available, Toast.LENGTH_LONG).show()
|
||||||
} else {
|
} else {
|
||||||
val shownotesCleaner = ShownotesCleaner(requireContext(), item?.description ?: "", media?.getDuration()?:0)
|
val shownotesCleaner = ShownotesCleaner(requireContext(), item?.description ?: "", media?.getDuration()?:0)
|
||||||
cleanedNotes = shownotesCleaner.processShownotes()
|
cleanedNotes = shownotesCleaner.processShownotes()
|
||||||
if (!cleanedNotes.isNullOrEmpty()) binding.webview.loadDataWithBaseURL("https://127.0.0.1", cleanedNotes!!, "text/html", "UTF-8", null)
|
if (!cleanedNotes.isNullOrEmpty()) shownoteView.loadDataWithBaseURL("https://127.0.0.1", cleanedNotes!!, "text/html", "UTF-8", null)
|
||||||
else Toast.makeText(context, R.string.web_content_not_available, Toast.LENGTH_LONG).show()
|
else Toast.makeText(context, R.string.web_content_not_available, Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,7 @@ object IntentUtils {
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun isCallable(context: Context, intent: Intent?): Boolean {
|
fun isCallable(context: Context, intent: Intent?): Boolean {
|
||||||
val list = context.packageManager.queryIntentActivities(intent!!,
|
val list = context.packageManager.queryIntentActivities(intent!!, PackageManager.MATCH_DEFAULT_ONLY)
|
||||||
PackageManager.MATCH_DEFAULT_ONLY)
|
|
||||||
for (info in list) {
|
for (info in list) {
|
||||||
if (info.activityInfo.exported) return true
|
if (info.activityInfo.exported) return true
|
||||||
}
|
}
|
||||||
|
@ -32,6 +31,7 @@ object IntentUtils {
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun openInBrowser(context: Context, url: String) {
|
fun openInBrowser(context: Context, url: String) {
|
||||||
|
Log.d(TAG, "url: $url")
|
||||||
try {
|
try {
|
||||||
val myIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
val myIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||||
myIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
myIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
|
|
@ -3,6 +3,7 @@ package ac.mdiq.podcini.util.event.playback
|
||||||
class PlaybackServiceEvent(@JvmField val action: Action) {
|
class PlaybackServiceEvent(@JvmField val action: Action) {
|
||||||
enum class Action {
|
enum class Action {
|
||||||
SERVICE_STARTED,
|
SERVICE_STARTED,
|
||||||
SERVICE_SHUT_DOWN
|
SERVICE_SHUT_DOWN,
|
||||||
|
// SERVICE_RESTARTED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M10.62,7.01V15.99L7.5,15.09"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="?attr/action_icon_color"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M16.5,7L13.35,7.45V12.4L16.5,11.95V15.1L12.9,16"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="?attr/action_icon_color"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M3.32,4.91L3.12,3.11C3.05,2.52 3.52,2 4.11,2H19.88C20.48,2 20.94,2.52 20.87,3.11L19.07,19.33C19.03,19.73 18.74,20.07 18.35,20.18L12.27,21.92C12.09,21.97 11.9,21.97 11.72,21.92L5.64,20.18C5.25,20.07 4.97,19.73 4.92,19.33L3.82,9.41"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="?attr/action_icon_color"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
|
@ -10,6 +10,13 @@
|
||||||
custom:showAsAction="always">
|
custom:showAsAction="always">
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/switchJS"
|
||||||
|
android:icon="@drawable/javascript_icon_245402"
|
||||||
|
android:title="@string/javasript_label"
|
||||||
|
custom:showAsAction="always">
|
||||||
|
</item>
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/switch_home"
|
android:id="@+id/switch_home"
|
||||||
android:icon="@drawable/baseline_home_24"
|
android:icon="@drawable/baseline_home_24"
|
||||||
|
@ -17,5 +24,10 @@
|
||||||
custom:showAsAction="always">
|
custom:showAsAction="always">
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/share_notes"
|
||||||
|
android:title="@string/share_notes_label"
|
||||||
|
custom:showAsAction="never">
|
||||||
|
</item>
|
||||||
|
|
||||||
</menu>
|
</menu>
|
|
@ -59,6 +59,8 @@
|
||||||
<string name="statistics_counting_total">Played in total</string>
|
<string name="statistics_counting_total">Played in total</string>
|
||||||
|
|
||||||
<string name="web_content_not_available">Web content is not available</string>
|
<string name="web_content_not_available">Web content is not available</string>
|
||||||
|
<string name="language_not_supported_by_tts">The language is not supported by TTS</string>
|
||||||
|
<string name="tts_init_failed">TTS init failed</string>
|
||||||
|
|
||||||
<string name="notification_permission_text">Since Android 13, top level notification is needed for normal refresh and playback. You may disallow notifications of sub-catergories at your wish.</string>
|
<string name="notification_permission_text">Since Android 13, top level notification is needed for normal refresh and playback. You may disallow notifications of sub-catergories at your wish.</string>
|
||||||
<string name="notification_permission_denied">You denied the permission.</string>
|
<string name="notification_permission_denied">You denied the permission.</string>
|
||||||
|
@ -224,6 +226,8 @@
|
||||||
<item quantity="other">%d downloaded episodes deleted.</item>
|
<item quantity="other">%d downloaded episodes deleted.</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
|
||||||
|
<string name="javasript_label">JavaScript</string>
|
||||||
|
|
||||||
<string name="no_action_label">No action</string>
|
<string name="no_action_label">No action</string>
|
||||||
|
|
||||||
<string name="removed_inbox_label">Removed from inbox</string>
|
<string name="removed_inbox_label">Removed from inbox</string>
|
||||||
|
|
|
@ -310,3 +310,12 @@
|
||||||
* when global auto download setting is enabled, no existing feed is automatically included for auto download
|
* when global auto download setting is enabled, no existing feed is automatically included for auto download
|
||||||
* when subscribing a new feed, there an option for auto download
|
* when subscribing a new feed, there an option for auto download
|
||||||
* new episode of a feed is auto downloaded at a feed refresh only when both global and feed settings for auto download are enabled
|
* new episode of a feed is auto downloaded at a feed refresh only when both global and feed settings for auto download are enabled
|
||||||
|
|
||||||
|
## 4.9.5
|
||||||
|
|
||||||
|
* added action bar option in episode home view to switch on/off JavaScript
|
||||||
|
* added share notes menu item in reader mode of episode home view
|
||||||
|
* TTS speed uses playback speed of the feed or 1.0
|
||||||
|
* on player detailed view, if showing episode home reader content, then "share notes" shares the reader content
|
||||||
|
* fixed bug of not re-playing a finished episode
|
||||||
|
* fixed (possibly) bug of marking multiple items played when one is finished playing
|
|
@ -0,0 +1,9 @@
|
||||||
|
|
||||||
|
Version 4.9.5 brings several changes:
|
||||||
|
|
||||||
|
* added action bar option in episode home view to switch on/off JavaScript
|
||||||
|
* added share notes menu item in reader mode of episode home view
|
||||||
|
* TTS speed uses playback speed of the feed or 1.0
|
||||||
|
* on player detailed view, if showing episode home reader content, then "share notes" shares the reader content
|
||||||
|
* fixed bug of not re-playing a finished episode
|
||||||
|
* fixed (possibly) bug of marking multiple items played when one is finished playing
|
Loading…
Reference in New Issue