4.8.0 commit
This commit is contained in:
parent
26115b436f
commit
1f8bb954a0
|
@ -60,6 +60,10 @@ Even so, the database remains backward compatible, and AntennaPod's db can be ea
|
||||||
* enabled intro- and end- skipping
|
* enabled intro- and end- skipping
|
||||||
* mark as played when finished
|
* mark as played when finished
|
||||||
* streamed media is added to queue and is resumed after restart
|
* streamed media is added to queue and is resumed after restart
|
||||||
|
* new video episode view, with video player on top and episode descriptions in portrait mode
|
||||||
|
* easy switches on video player to other video mode or audio only
|
||||||
|
* default video player mode setting in preferences
|
||||||
|
* when video mode is set to audio only, click on image on audio player on a video episode brings up the normal player detailed view
|
||||||
|
|
||||||
### Podcast/Episode list
|
### Podcast/Episode list
|
||||||
|
|
||||||
|
|
|
@ -149,8 +149,8 @@ android {
|
||||||
// Version code schema (not used):
|
// Version code schema (not used):
|
||||||
// "1.2.3-beta4" -> 1020304
|
// "1.2.3-beta4" -> 1020304
|
||||||
// "1.2.3" -> 1020395
|
// "1.2.3" -> 1020395
|
||||||
versionCode 3020129
|
versionCode 3020130
|
||||||
versionName "4.7.1"
|
versionName "4.8.0"
|
||||||
|
|
||||||
def commit = ""
|
def commit = ""
|
||||||
try {
|
try {
|
||||||
|
@ -238,6 +238,7 @@ dependencies {
|
||||||
implementation "androidx.work:work-runtime:2.9.0"
|
implementation "androidx.work:work-runtime:2.9.0"
|
||||||
implementation "androidx.core:core-splashscreen:1.0.1"
|
implementation "androidx.core:core-splashscreen:1.0.1"
|
||||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||||
|
implementation 'androidx.webkit:webkit:1.9.0'
|
||||||
|
|
||||||
implementation "com.google.android.material:material:1.11.0"
|
implementation "com.google.android.material:material:1.11.0"
|
||||||
|
|
||||||
|
|
|
@ -246,11 +246,12 @@
|
||||||
android:value="ac.mdiq.podcini.ui.activity.PreferenceActivity"/>
|
android:value="ac.mdiq.podcini.ui.activity.PreferenceActivity"/>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<!-- android:screenOrientation="sensorLandscape"-->
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.activity.VideoplayerActivity"
|
android:name=".ui.activity.VideoplayerActivity"
|
||||||
android:configChanges="keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize"
|
android:configChanges="keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize"
|
||||||
android:supportsPictureInPicture="true"
|
android:supportsPictureInPicture="true"
|
||||||
android:screenOrientation="sensorLandscape"
|
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
|
|
@ -267,6 +267,14 @@ abstract class PlaybackController(private val activity: FragmentActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ensureService() {
|
||||||
|
if (media == null) return
|
||||||
|
if (playbackService == null) {
|
||||||
|
PlaybackServiceStarter(activity, media!!).start()
|
||||||
|
Log.w(TAG, "playbackservice was null, restarted!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun playPause() {
|
fun playPause() {
|
||||||
if (media == null) return
|
if (media == null) return
|
||||||
if (playbackService == null) {
|
if (playbackService == null) {
|
||||||
|
|
|
@ -1626,6 +1626,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
mediaPlayer?.setPlaybackParams(speed, isSkipSilence)
|
mediaPlayer?.setPlaybackParams(speed, isSkipSilence)
|
||||||
} else {
|
} else {
|
||||||
if (codeArray != null && codeArray.size == 3) {
|
if (codeArray != null && codeArray.size == 3) {
|
||||||
|
Log.d(TAG, "setSpeed codeArray: ${codeArray[0]} ${codeArray[1]} ${codeArray[2]}")
|
||||||
if (codeArray[2]) setPlaybackSpeed(speed)
|
if (codeArray[2]) setPlaybackSpeed(speed)
|
||||||
if (codeArray[1]) {
|
if (codeArray[1]) {
|
||||||
var item = (playable as? FeedMedia)?.item ?: currentitem
|
var item = (playable as? FeedMedia)?.item ?: currentitem
|
||||||
|
|
|
@ -62,28 +62,21 @@ class PlaybackServiceNotificationBuilder(private val context: Context) {
|
||||||
val iconSize = (128 * context.resources.displayMetrics.density).toInt()
|
val iconSize = (128 * context.resources.displayMetrics.density).toInt()
|
||||||
val options = RequestOptions().centerCrop()
|
val options = RequestOptions().centerCrop()
|
||||||
try {
|
try {
|
||||||
var imgLoc = playable?.getImageLocation()
|
val imgLoc = playable?.getImageLocation()
|
||||||
when {
|
val imgLoc1 = ImageResourceUtils.getFallbackImageLocation(playable!!)
|
||||||
!imgLoc.isNullOrBlank() -> {
|
Log.d(TAG, "loadIcon imgLoc $imgLoc $imgLoc1")
|
||||||
cachedIcon = Glide.with(context)
|
cachedIcon = Glide.with(context)
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.load(imgLoc)
|
.load(imgLoc)
|
||||||
.apply(options)
|
.error(Glide.with(context)
|
||||||
.submit(iconSize, iconSize)
|
|
||||||
.get()
|
|
||||||
}
|
|
||||||
playable != null -> {
|
|
||||||
imgLoc = ImageResourceUtils.getFallbackImageLocation(playable!!)
|
|
||||||
if (!imgLoc.isNullOrBlank()) {
|
|
||||||
cachedIcon = Glide.with(context)
|
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.load(imgLoc)
|
.load(imgLoc1)
|
||||||
|
.apply(options)
|
||||||
|
.submit(iconSize, iconSize)
|
||||||
|
.get())
|
||||||
.apply(options)
|
.apply(options)
|
||||||
.submit(iconSize, iconSize)
|
.submit(iconSize, iconSize)
|
||||||
.get()
|
.get()
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (ignore: InterruptedException) {
|
} catch (ignore: InterruptedException) {
|
||||||
Log.e(TAG, "Media icon loader was interrupted")
|
Log.e(TAG, "Media icon loader was interrupted")
|
||||||
} catch (tr: Throwable) {
|
} catch (tr: Throwable) {
|
||||||
|
|
|
@ -111,6 +111,7 @@ object UserPreferences {
|
||||||
private const val PREF_FAST_FORWARD_SECS = "prefFastForwardSecs"
|
private const val PREF_FAST_FORWARD_SECS = "prefFastForwardSecs"
|
||||||
private const val PREF_REWIND_SECS = "prefRewindSecs"
|
private const val PREF_REWIND_SECS = "prefRewindSecs"
|
||||||
private const val PREF_QUEUE_LOCKED = "prefQueueLocked"
|
private const val PREF_QUEUE_LOCKED = "prefQueueLocked"
|
||||||
|
private const val PREF_VIDEO_MODE = "prefVideoPlaybackMode"
|
||||||
|
|
||||||
// Experimental
|
// Experimental
|
||||||
const val EPISODE_CLEANUP_QUEUE: Int = -1
|
const val EPISODE_CLEANUP_QUEUE: Int = -1
|
||||||
|
@ -410,6 +411,17 @@ object UserPreferences {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val videoPlayMode: Int
|
||||||
|
get() {
|
||||||
|
try {
|
||||||
|
return prefs.getString(PREF_VIDEO_MODE, "1")!!.toInt()
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
Log.e(TAG, Log.getStackTraceString(e))
|
||||||
|
setVideoMode(1)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
var videoPlaybackSpeed: Float
|
var videoPlaybackSpeed: Float
|
||||||
get() {
|
get() {
|
||||||
|
@ -661,6 +673,13 @@ object UserPreferences {
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun setVideoMode(mode: Int) {
|
||||||
|
prefs.edit()
|
||||||
|
.putString(PREF_VIDEO_MODE, mode.toString())
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun setAutodownloadSelectedNetworks(value: Array<String?>?) {
|
fun setAutodownloadSelectedNetworks(value: Array<String?>?) {
|
||||||
prefs.edit()
|
prefs.edit()
|
||||||
|
|
|
@ -5,10 +5,7 @@ import ac.mdiq.podcini.preferences.UsageStatistics
|
||||||
import ac.mdiq.podcini.preferences.UsageStatistics.doNotAskAgain
|
import ac.mdiq.podcini.preferences.UsageStatistics.doNotAskAgain
|
||||||
import ac.mdiq.podcini.preferences.UserPreferences
|
import ac.mdiq.podcini.preferences.UserPreferences
|
||||||
import ac.mdiq.podcini.ui.activity.PreferenceActivity
|
import ac.mdiq.podcini.ui.activity.PreferenceActivity
|
||||||
import ac.mdiq.podcini.ui.dialog.EditFallbackSpeedDialog
|
import ac.mdiq.podcini.ui.dialog.*
|
||||||
import ac.mdiq.podcini.ui.dialog.EditForwardSpeedDialog
|
|
||||||
import ac.mdiq.podcini.ui.dialog.SkipPreferenceDialog
|
|
||||||
import ac.mdiq.podcini.ui.dialog.VariableSpeedDialog
|
|
||||||
import ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent
|
import ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
@ -47,6 +44,12 @@ class PlaybackPreferencesFragment : PreferenceFragmentCompat() {
|
||||||
SkipPreferenceDialog.showSkipPreference(requireContext(), SkipPreferenceDialog.SkipDirection.SKIP_REWIND, null)
|
SkipPreferenceDialog.showSkipPreference(requireContext(), SkipPreferenceDialog.SkipDirection.SKIP_REWIND, null)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
findPreference<Preference>(PREF_PLAYBACK_VIDEO_MODE_LAUNCHER)?.onPreferenceClickListener =
|
||||||
|
Preference.OnPreferenceClickListener {
|
||||||
|
VideoModeDialog.showDialog(requireContext())
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
findPreference<Preference>(PREF_PLAYBACK_SPEED_FORWARD_LAUNCHER)!!.onPreferenceClickListener =
|
findPreference<Preference>(PREF_PLAYBACK_SPEED_FORWARD_LAUNCHER)!!.onPreferenceClickListener =
|
||||||
Preference.OnPreferenceClickListener {
|
Preference.OnPreferenceClickListener {
|
||||||
EditForwardSpeedDialog(requireActivity()).show()
|
EditForwardSpeedDialog(requireActivity()).show()
|
||||||
|
@ -138,5 +141,6 @@ class PlaybackPreferencesFragment : PreferenceFragmentCompat() {
|
||||||
private const val PREF_PLAYBACK_SPEED_FORWARD_LAUNCHER = "prefPlaybackSpeedForwardLauncher"
|
private const val PREF_PLAYBACK_SPEED_FORWARD_LAUNCHER = "prefPlaybackSpeedForwardLauncher"
|
||||||
private const val PREF_PLAYBACK_FAST_FORWARD_DELTA_LAUNCHER = "prefPlaybackFastForwardDeltaLauncher"
|
private const val PREF_PLAYBACK_FAST_FORWARD_DELTA_LAUNCHER = "prefPlaybackFastForwardDeltaLauncher"
|
||||||
private const val PREF_PLAYBACK_PREFER_STREAMING = "prefStreamOverDownload"
|
private const val PREF_PLAYBACK_PREFER_STREAMING = "prefStreamOverDownload"
|
||||||
|
private const val PREF_PLAYBACK_VIDEO_MODE_LAUNCHER = "prefPlaybackVideoModeLauncher"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -286,6 +286,9 @@ class MainActivity : CastEnabledActivity() {
|
||||||
}
|
}
|
||||||
override fun onSlide(view: View, slideOffset: Float) {
|
override fun onSlide(view: View, slideOffset: Float) {
|
||||||
val audioPlayer = supportFragmentManager.findFragmentByTag(AudioPlayerFragment.TAG) as? AudioPlayerFragment ?: return
|
val audioPlayer = supportFragmentManager.findFragmentByTag(AudioPlayerFragment.TAG) as? AudioPlayerFragment ?: return
|
||||||
|
// if (slideOffset == 0.0f) { //STATE_COLLAPSED
|
||||||
|
// audioPlayer.scrollToTop()
|
||||||
|
// }
|
||||||
audioPlayer.fadePlayerToToolbar(slideOffset)
|
audioPlayer.fadePlayerToToolbar(slideOffset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ class PlaybackSpeedDialogActivity : AppCompatActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
setTheme(getTranslucentTheme(this))
|
setTheme(getTranslucentTheme(this))
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
val speedDialog: VariableSpeedDialog? = VariableSpeedDialog.newInstance(booleanArrayOf(false, false, true), 2)
|
val speedDialog: VariableSpeedDialog? = VariableSpeedDialog.newInstance(booleanArrayOf(true, true, true), 2)
|
||||||
speedDialog?.show(supportFragmentManager, null)
|
speedDialog?.show(supportFragmentManager, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -113,15 +113,13 @@ class SelectSubscriptionActivity : AppCompatActivity() {
|
||||||
.apply(RequestOptions.overrideOf(iconSize, iconSize))
|
.apply(RequestOptions.overrideOf(iconSize, iconSize))
|
||||||
.listener(object : RequestListener<Bitmap?> {
|
.listener(object : RequestListener<Bitmap?> {
|
||||||
@UnstableApi override fun onLoadFailed(e: GlideException?, model: Any?,
|
@UnstableApi override fun onLoadFailed(e: GlideException?, model: Any?,
|
||||||
target: Target<Bitmap?>, isFirstResource: Boolean
|
target: Target<Bitmap?>, isFirstResource: Boolean): Boolean {
|
||||||
): Boolean {
|
|
||||||
addShortcut(feed, null)
|
addShortcut(feed, null)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@UnstableApi override fun onResourceReady(resource: Bitmap, model: Any,
|
@UnstableApi override fun onResourceReady(resource: Bitmap, model: Any,
|
||||||
target: Target<Bitmap?>, dataSource: DataSource, isFirstResource: Boolean
|
target: Target<Bitmap?>, dataSource: DataSource, isFirstResource: Boolean): Boolean {
|
||||||
): Boolean {
|
|
||||||
addShortcut(feed, resource)
|
addShortcut(feed, resource)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,107 +1,106 @@
|
||||||
package ac.mdiq.podcini.ui.activity
|
package ac.mdiq.podcini.ui.activity
|
||||||
|
|
||||||
import ac.mdiq.podcini.R
|
import ac.mdiq.podcini.R
|
||||||
|
import ac.mdiq.podcini.databinding.VideoplayerActivityBinding
|
||||||
|
import ac.mdiq.podcini.playback.cast.CastEnabledActivity
|
||||||
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.getPlayerActivityIntent
|
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.getPlayerActivityIntent
|
||||||
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.isCasting
|
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.isCasting
|
||||||
import ac.mdiq.podcini.storage.DBReader
|
import ac.mdiq.podcini.preferences.UserPreferences.videoPlayMode
|
||||||
import ac.mdiq.podcini.storage.DBWriter
|
import ac.mdiq.podcini.storage.DBWriter
|
||||||
import ac.mdiq.podcini.util.Converter.getDurationStringLong
|
|
||||||
import ac.mdiq.podcini.util.FeedItemUtil.getLinkWithFallback
|
|
||||||
import ac.mdiq.podcini.util.IntentUtils.openInBrowser
|
|
||||||
import ac.mdiq.podcini.util.ShareUtils.hasLinkToShare
|
|
||||||
import ac.mdiq.podcini.util.TimeSpeedConverter
|
|
||||||
import ac.mdiq.podcini.ui.utils.PictureInPictureUtil
|
|
||||||
import ac.mdiq.podcini.playback.PlaybackController
|
|
||||||
import ac.mdiq.podcini.databinding.VideoplayerActivityBinding
|
|
||||||
import ac.mdiq.podcini.ui.dialog.*
|
|
||||||
import ac.mdiq.podcini.util.event.playback.BufferUpdateEvent
|
|
||||||
import ac.mdiq.podcini.util.event.playback.PlaybackPositionEvent
|
|
||||||
import ac.mdiq.podcini.util.event.playback.PlaybackServiceEvent
|
|
||||||
import ac.mdiq.podcini.util.event.playback.SleepTimerUpdatedEvent
|
|
||||||
import ac.mdiq.podcini.ui.fragment.ChaptersFragment
|
|
||||||
import ac.mdiq.podcini.storage.model.feed.FeedItem
|
import ac.mdiq.podcini.storage.model.feed.FeedItem
|
||||||
import ac.mdiq.podcini.storage.model.feed.FeedMedia
|
import ac.mdiq.podcini.storage.model.feed.FeedMedia
|
||||||
import ac.mdiq.podcini.storage.model.playback.Playable
|
import ac.mdiq.podcini.storage.model.playback.Playable
|
||||||
import ac.mdiq.podcini.playback.base.PlayerStatus
|
import ac.mdiq.podcini.ui.dialog.*
|
||||||
import ac.mdiq.podcini.playback.cast.CastEnabledActivity
|
import ac.mdiq.podcini.ui.fragment.ChaptersFragment
|
||||||
import ac.mdiq.podcini.preferences.UserPreferences.fastForwardSecs
|
import ac.mdiq.podcini.ui.fragment.VideoEpisodeFragment
|
||||||
import ac.mdiq.podcini.preferences.UserPreferences.rewindSecs
|
import ac.mdiq.podcini.ui.utils.PictureInPictureUtil
|
||||||
import ac.mdiq.podcini.preferences.UserPreferences.setShowRemainTimeSetting
|
import ac.mdiq.podcini.util.FeedItemUtil.getLinkWithFallback
|
||||||
import ac.mdiq.podcini.preferences.UserPreferences.shouldShowRemainingTime
|
import ac.mdiq.podcini.util.IntentUtils.openInBrowser
|
||||||
import ac.mdiq.podcini.ui.activity.appstartintent.MainActivityStarter
|
import ac.mdiq.podcini.util.ShareUtils.hasLinkToShare
|
||||||
import ac.mdiq.podcini.util.event.MessageEvent
|
import ac.mdiq.podcini.util.event.MessageEvent
|
||||||
import ac.mdiq.podcini.util.event.PlayerErrorEvent
|
import ac.mdiq.podcini.util.event.PlayerErrorEvent
|
||||||
|
import ac.mdiq.podcini.util.event.playback.PlaybackServiceEvent
|
||||||
|
import ac.mdiq.podcini.util.event.playback.SleepTimerUpdatedEvent
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.ActivityInfo
|
||||||
import android.graphics.PixelFormat
|
import android.graphics.PixelFormat
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
|
||||||
import android.os.Looper
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.view.View.OnTouchListener
|
import android.view.MenuItem.SHOW_AS_ACTION_NEVER
|
||||||
import android.view.animation.*
|
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.FrameLayout
|
|
||||||
import android.widget.SeekBar
|
|
||||||
import android.widget.SeekBar.OnSeekBarChangeListener
|
|
||||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import io.reactivex.Observable
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
|
||||||
import io.reactivex.disposables.Disposable
|
|
||||||
import io.reactivex.schedulers.Schedulers
|
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.greenrobot.eventbus.Subscribe
|
import org.greenrobot.eventbus.Subscribe
|
||||||
import org.greenrobot.eventbus.ThreadMode
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity for playing video files.
|
* Activity for playing video files.
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
|
class VideoplayerActivity : CastEnabledActivity() {
|
||||||
|
|
||||||
private var _binding: VideoplayerActivityBinding? = null
|
private var _binding: VideoplayerActivityBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
/**
|
lateinit var videoEpisodeFragment: VideoEpisodeFragment
|
||||||
* True if video controls are currently visible.
|
|
||||||
*/
|
var videoMode = 0
|
||||||
private var videoControlsShowing = true
|
var switchToAudioOnly = false
|
||||||
private var videoSurfaceCreated = false
|
|
||||||
private var destroyingDueToReload = false
|
|
||||||
private var lastScreenTap: Long = 0
|
|
||||||
private val videoControlsHider = Handler(Looper.getMainLooper())
|
|
||||||
private var controller: PlaybackController? = null
|
|
||||||
private var showTimeLeft = false
|
|
||||||
private var isFavorite = false
|
|
||||||
private var switchToAudioOnly = false
|
|
||||||
private var disposable: Disposable? = null
|
|
||||||
private var prog = 0f
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
|
||||||
|
videoMode = intent.getIntExtra("fullScreenMode",0)
|
||||||
|
if (videoMode == 0) {
|
||||||
|
videoMode = videoPlayMode
|
||||||
|
if (videoMode == AUDIO_ONLY) {
|
||||||
|
switchToAudioOnly = true
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
if (videoMode != FULL_SCREEN_VIEW && videoMode != WINDOW_VIEW) {
|
||||||
|
Log.i(TAG, "videoMode not selected, use window mode")
|
||||||
|
videoMode = WINDOW_VIEW
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
when (videoMode) {
|
||||||
|
FULL_SCREEN_VIEW -> {
|
||||||
window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN)
|
window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN)
|
||||||
// has to be called before setting layout content
|
// has to be called before setting layout content
|
||||||
supportRequestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY)
|
supportRequestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY)
|
||||||
setTheme(R.style.Theme_Podcini_VideoPlayer)
|
setTheme(R.style.Theme_Podcini_VideoPlayer)
|
||||||
|
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||||
|
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
||||||
|
window.setFormat(PixelFormat.TRANSPARENT)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
window.setFormat(PixelFormat.TRANSPARENT)
|
|
||||||
_binding = VideoplayerActivityBinding.inflate(LayoutInflater.from(this))
|
_binding = VideoplayerActivityBinding.inflate(LayoutInflater.from(this))
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
setupView()
|
|
||||||
supportActionBar?.setBackgroundDrawable(ColorDrawable(-0x80000000))
|
supportActionBar?.setBackgroundDrawable(ColorDrawable(-0x80000000))
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
|
|
||||||
controller = newPlaybackController()
|
val fm = supportFragmentManager
|
||||||
controller!!.init()
|
val transaction = fm.beginTransaction()
|
||||||
loadMediaInfo()
|
videoEpisodeFragment = VideoEpisodeFragment()
|
||||||
|
transaction.replace(R.id.main_view, videoEpisodeFragment, VideoEpisodeFragment.TAG)
|
||||||
|
transaction.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
|
@ -111,7 +110,7 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
|
||||||
if (isCasting) {
|
if (isCasting) {
|
||||||
val intent = getPlayerActivityIntent(this)
|
val intent = getPlayerActivityIntent(this)
|
||||||
if (intent.component?.className != VideoplayerActivity::class.java.name) {
|
if (intent.component?.className != VideoplayerActivity::class.java.name) {
|
||||||
destroyingDueToReload = true
|
videoEpisodeFragment.destroyingDueToReload = true
|
||||||
finish()
|
finish()
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
|
@ -120,21 +119,17 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN)
|
||||||
|
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
||||||
|
window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN)
|
||||||
_binding = null
|
_binding = null
|
||||||
controller?.release()
|
|
||||||
controller = null // prevent leak
|
|
||||||
disposable?.dispose()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
EventBus.getDefault().unregister(this)
|
EventBus.getDefault().unregister(this)
|
||||||
super.onStop()
|
super.onStop()
|
||||||
if (!PictureInPictureUtil.isInPictureInPictureMode(this)) {
|
|
||||||
videoControlsHider.removeCallbacks(hideVideoControls)
|
|
||||||
}
|
|
||||||
// Controller released; we will not receive buffering updates
|
|
||||||
binding.progressBar.visibility = View.GONE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override fun onUserLeaveHint() {
|
public override fun onUserLeaveHint() {
|
||||||
|
@ -146,20 +141,9 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
onPositionObserverUpdate()
|
|
||||||
EventBus.getDefault().register(this)
|
EventBus.getDefault().register(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@UnstableApi
|
|
||||||
override fun onPause() {
|
|
||||||
if (!PictureInPictureUtil.isInPictureInPictureMode(this)) {
|
|
||||||
if (controller?.status == PlayerStatus.PLAYING) {
|
|
||||||
controller!!.pause()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.onPause()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTrimMemory(level: Int) {
|
override fun onTrimMemory(level: Int) {
|
||||||
super.onTrimMemory(level)
|
super.onTrimMemory(level)
|
||||||
Glide.get(this).trimMemory(level)
|
Glide.get(this).trimMemory(level)
|
||||||
|
@ -170,47 +154,11 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
|
||||||
Glide.get(this).clearMemory()
|
Glide.get(this).clearMemory()
|
||||||
}
|
}
|
||||||
|
|
||||||
@UnstableApi
|
fun toggleViews() {
|
||||||
private fun newPlaybackController(): PlaybackController {
|
val newIntent = Intent(this, VideoplayerActivity::class.java)
|
||||||
return object : PlaybackController(this@VideoplayerActivity) {
|
newIntent.putExtra("fullScreenMode", if (videoMode == FULL_SCREEN_VIEW) WINDOW_VIEW else FULL_SCREEN_VIEW)
|
||||||
override fun updatePlayButtonShowsPlay(showPlay: Boolean) {
|
|
||||||
binding.playButton.setIsShowPlay(showPlay)
|
|
||||||
if (showPlay) {
|
|
||||||
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
||||||
} else {
|
|
||||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
||||||
setupVideoAspectRatio()
|
|
||||||
if (videoSurfaceCreated && controller != null) {
|
|
||||||
Log.d(TAG, "Videosurface already created, setting videosurface now")
|
|
||||||
controller!!.setVideoSurface(binding.videoView.holder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun loadMediaInfo() {
|
|
||||||
this@VideoplayerActivity.loadMediaInfo()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPlaybackEnd() {
|
|
||||||
finish()
|
finish()
|
||||||
}
|
startActivity(newIntent)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
|
||||||
@Suppress("unused")
|
|
||||||
fun bufferUpdate(event: BufferUpdateEvent) {
|
|
||||||
when {
|
|
||||||
event.hasStarted() -> {
|
|
||||||
binding.progressBar.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
event.hasEnded() -> {
|
|
||||||
binding.progressBar.visibility = View.INVISIBLE
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
binding.sbPosition.secondaryProgress = (event.progress * binding.sbPosition.max).toInt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
|
@ -221,266 +169,6 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@UnstableApi
|
|
||||||
private fun loadMediaInfo() {
|
|
||||||
Log.d(TAG, "loadMediaInfo()")
|
|
||||||
if (controller?.getMedia() == null) return
|
|
||||||
|
|
||||||
if (controller!!.status == PlayerStatus.PLAYING && !controller!!.isPlayingVideoLocally) {
|
|
||||||
Log.d(TAG, "Closing, no longer video")
|
|
||||||
destroyingDueToReload = true
|
|
||||||
finish()
|
|
||||||
MainActivityStarter(this).withOpenPlayer().start()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
showTimeLeft = shouldShowRemainingTime()
|
|
||||||
onPositionObserverUpdate()
|
|
||||||
checkFavorite()
|
|
||||||
val media = controller!!.getMedia()
|
|
||||||
if (media != null) {
|
|
||||||
supportActionBar!!.subtitle = media.getEpisodeTitle()
|
|
||||||
supportActionBar!!.title = media.getFeedTitle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@UnstableApi
|
|
||||||
private fun setupView() {
|
|
||||||
showTimeLeft = shouldShowRemainingTime()
|
|
||||||
Log.d("timeleft", if (showTimeLeft) "true" else "false")
|
|
||||||
binding.durationLabel.setOnClickListener {
|
|
||||||
showTimeLeft = !showTimeLeft
|
|
||||||
val media = controller?.getMedia() ?: return@setOnClickListener
|
|
||||||
|
|
||||||
val converter = TimeSpeedConverter(controller!!.currentPlaybackSpeedMultiplier)
|
|
||||||
val length: String
|
|
||||||
if (showTimeLeft) {
|
|
||||||
val remainingTime = converter.convert(media.getDuration() - media.getPosition())
|
|
||||||
length = "-" + getDurationStringLong(remainingTime)
|
|
||||||
} else {
|
|
||||||
val duration = converter.convert(media.getDuration())
|
|
||||||
length = getDurationStringLong(duration)
|
|
||||||
}
|
|
||||||
binding.durationLabel.text = length
|
|
||||||
|
|
||||||
setShowRemainTimeSetting(showTimeLeft)
|
|
||||||
Log.d("timeleft on click", if (showTimeLeft) "true" else "false")
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.sbPosition.setOnSeekBarChangeListener(this)
|
|
||||||
binding.rewindButton.setOnClickListener { onRewind() }
|
|
||||||
binding.rewindButton.setOnLongClickListener {
|
|
||||||
SkipPreferenceDialog.showSkipPreference(this@VideoplayerActivity,
|
|
||||||
SkipPreferenceDialog.SkipDirection.SKIP_REWIND, null)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
binding.playButton.setIsVideoScreen(true)
|
|
||||||
binding.playButton.setOnClickListener { onPlayPause() }
|
|
||||||
binding.fastForwardButton.setOnClickListener { onFastForward() }
|
|
||||||
binding.fastForwardButton.setOnLongClickListener {
|
|
||||||
SkipPreferenceDialog.showSkipPreference(this@VideoplayerActivity,
|
|
||||||
SkipPreferenceDialog.SkipDirection.SKIP_FORWARD, null)
|
|
||||||
false
|
|
||||||
}
|
|
||||||
// To suppress touches directly below the slider
|
|
||||||
binding.bottomControlsContainer.setOnTouchListener { _: View?, _: MotionEvent? -> true }
|
|
||||||
binding.bottomControlsContainer.fitsSystemWindows = true
|
|
||||||
binding.videoView.holder.addCallback(surfaceHolderCallback)
|
|
||||||
binding.videoView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
|
||||||
|
|
||||||
setupVideoControlsToggler()
|
|
||||||
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
|
||||||
|
|
||||||
binding.videoPlayerContainer.setOnTouchListener(onVideoviewTouched)
|
|
||||||
binding.videoPlayerContainer.viewTreeObserver.addOnGlobalLayoutListener {
|
|
||||||
binding.videoView.setAvailableSize(
|
|
||||||
binding.videoPlayerContainer.width.toFloat(), binding.videoPlayerContainer.height.toFloat())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val hideVideoControls = Runnable {
|
|
||||||
if (videoControlsShowing) {
|
|
||||||
Log.d(TAG, "Hiding video controls")
|
|
||||||
supportActionBar?.hide()
|
|
||||||
hideVideoControls(true)
|
|
||||||
videoControlsShowing = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val onVideoviewTouched = OnTouchListener { v: View, event: MotionEvent ->
|
|
||||||
if (event.action != MotionEvent.ACTION_DOWN) return@OnTouchListener false
|
|
||||||
|
|
||||||
if (PictureInPictureUtil.isInPictureInPictureMode(this)) return@OnTouchListener true
|
|
||||||
|
|
||||||
videoControlsHider.removeCallbacks(hideVideoControls)
|
|
||||||
|
|
||||||
if (System.currentTimeMillis() - lastScreenTap < 300) {
|
|
||||||
if (event.x > v.measuredWidth / 2.0f) {
|
|
||||||
onFastForward()
|
|
||||||
showSkipAnimation(true)
|
|
||||||
} else {
|
|
||||||
onRewind()
|
|
||||||
showSkipAnimation(false)
|
|
||||||
}
|
|
||||||
if (videoControlsShowing) {
|
|
||||||
supportActionBar?.hide()
|
|
||||||
hideVideoControls(false)
|
|
||||||
videoControlsShowing = false
|
|
||||||
}
|
|
||||||
return@OnTouchListener true
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleVideoControlsVisibility()
|
|
||||||
if (videoControlsShowing) setupVideoControlsToggler()
|
|
||||||
|
|
||||||
lastScreenTap = System.currentTimeMillis()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showSkipAnimation(isForward: Boolean) {
|
|
||||||
val skipAnimation = AnimationSet(true)
|
|
||||||
skipAnimation.addAnimation(ScaleAnimation(1f, 2f, 1f, 2f,
|
|
||||||
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f))
|
|
||||||
skipAnimation.addAnimation(AlphaAnimation(1f, 0f))
|
|
||||||
skipAnimation.fillAfter = false
|
|
||||||
skipAnimation.duration = 800
|
|
||||||
|
|
||||||
val params = binding.skipAnimationImage.layoutParams as FrameLayout.LayoutParams
|
|
||||||
if (isForward) {
|
|
||||||
binding.skipAnimationImage.setImageResource(R.drawable.ic_fast_forward_video_white)
|
|
||||||
params.gravity = Gravity.RIGHT or Gravity.CENTER_VERTICAL
|
|
||||||
} else {
|
|
||||||
binding.skipAnimationImage.setImageResource(R.drawable.ic_fast_rewind_video_white)
|
|
||||||
params.gravity = Gravity.LEFT or Gravity.CENTER_VERTICAL
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.skipAnimationImage.visibility = View.VISIBLE
|
|
||||||
binding.skipAnimationImage.layoutParams = params
|
|
||||||
binding.skipAnimationImage.startAnimation(skipAnimation)
|
|
||||||
skipAnimation.setAnimationListener(object : Animation.AnimationListener {
|
|
||||||
override fun onAnimationStart(animation: Animation) {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAnimationEnd(animation: Animation) {
|
|
||||||
binding.skipAnimationImage.visibility = View.GONE
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAnimationRepeat(animation: Animation) {
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupVideoControlsToggler() {
|
|
||||||
videoControlsHider.removeCallbacks(hideVideoControls)
|
|
||||||
videoControlsHider.postDelayed(hideVideoControls, 2500)
|
|
||||||
}
|
|
||||||
|
|
||||||
@UnstableApi
|
|
||||||
private fun setupVideoAspectRatio() {
|
|
||||||
if (videoSurfaceCreated && controller != null) {
|
|
||||||
val videoSize = controller!!.videoSize
|
|
||||||
if (videoSize != null && videoSize.first > 0 && videoSize.second > 0) {
|
|
||||||
Log.d(TAG, "Width,height of video: " + videoSize.first + ", " + videoSize.second)
|
|
||||||
binding.videoView.setVideoSize(videoSize.first, videoSize.second)
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "Could not determine video size")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun toggleVideoControlsVisibility() {
|
|
||||||
if (videoControlsShowing) {
|
|
||||||
supportActionBar?.hide()
|
|
||||||
hideVideoControls(true)
|
|
||||||
} else {
|
|
||||||
supportActionBar?.show()
|
|
||||||
showVideoControls()
|
|
||||||
}
|
|
||||||
videoControlsShowing = !videoControlsShowing
|
|
||||||
}
|
|
||||||
|
|
||||||
@UnstableApi
|
|
||||||
fun onRewind() {
|
|
||||||
if (controller == null) return
|
|
||||||
|
|
||||||
val curr = controller!!.position
|
|
||||||
controller!!.seekTo(curr - rewindSecs * 1000)
|
|
||||||
setupVideoControlsToggler()
|
|
||||||
}
|
|
||||||
|
|
||||||
@UnstableApi
|
|
||||||
fun onPlayPause() {
|
|
||||||
if (controller == null) return
|
|
||||||
|
|
||||||
controller!!.playPause()
|
|
||||||
setupVideoControlsToggler()
|
|
||||||
}
|
|
||||||
|
|
||||||
@UnstableApi
|
|
||||||
fun onFastForward() {
|
|
||||||
if (controller == null) return
|
|
||||||
|
|
||||||
val curr = controller!!.position
|
|
||||||
controller!!.seekTo(curr + fastForwardSecs * 1000)
|
|
||||||
setupVideoControlsToggler()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val surfaceHolderCallback: SurfaceHolder.Callback = object : SurfaceHolder.Callback {
|
|
||||||
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
|
|
||||||
holder.setFixedSize(width, height)
|
|
||||||
}
|
|
||||||
|
|
||||||
@UnstableApi
|
|
||||||
override fun surfaceCreated(holder: SurfaceHolder) {
|
|
||||||
Log.d(TAG, "Videoview holder created")
|
|
||||||
videoSurfaceCreated = true
|
|
||||||
if (controller?.status == PlayerStatus.PLAYING) {
|
|
||||||
controller!!.setVideoSurface(holder)
|
|
||||||
}
|
|
||||||
setupVideoAspectRatio()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
|
||||||
Log.d(TAG, "Videosurface was destroyed")
|
|
||||||
videoSurfaceCreated = false
|
|
||||||
if (controller != null && !destroyingDueToReload && !switchToAudioOnly) {
|
|
||||||
controller!!.notifyVideoSurfaceAbandoned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showVideoControls() {
|
|
||||||
binding.bottomControlsContainer.visibility = View.VISIBLE
|
|
||||||
binding.controlsContainer.visibility = View.VISIBLE
|
|
||||||
val animation = AnimationUtils.loadAnimation(this, R.anim.fade_in)
|
|
||||||
if (animation != null) {
|
|
||||||
binding.bottomControlsContainer.startAnimation(animation)
|
|
||||||
binding.controlsContainer.startAnimation(animation)
|
|
||||||
}
|
|
||||||
binding.videoView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun hideVideoControls(showAnimation: Boolean) {
|
|
||||||
if (showAnimation) {
|
|
||||||
val animation = AnimationUtils.loadAnimation(this, R.anim.fade_out)
|
|
||||||
if (animation != null) {
|
|
||||||
binding.bottomControlsContainer.startAnimation(animation)
|
|
||||||
binding.controlsContainer.startAnimation(animation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LOW_PROFILE
|
|
||||||
or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
|
||||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)
|
|
||||||
binding.bottomControlsContainer.fitsSystemWindows = true
|
|
||||||
|
|
||||||
binding.bottomControlsContainer.visibility = View.GONE
|
|
||||||
binding.controlsContainer.visibility = View.GONE
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
|
||||||
fun onEventMainThread(event: PlaybackPositionEvent?) {
|
|
||||||
onPositionObserverUpdate()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
fun onPlaybackServiceChanged(event: PlaybackServiceEvent) {
|
fun onPlaybackServiceChanged(event: PlaybackServiceEvent) {
|
||||||
if (event.action == PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN) {
|
if (event.action == PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN) {
|
||||||
|
@ -516,9 +204,9 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
||||||
super.onPrepareOptionsMenu(menu)
|
super.onPrepareOptionsMenu(menu)
|
||||||
if (controller == null) return false
|
val controller = videoEpisodeFragment.controller ?: return false
|
||||||
|
|
||||||
val media = controller!!.getMedia()
|
val media = controller.getMedia()
|
||||||
val isFeedMedia = (media is FeedMedia)
|
val isFeedMedia = (media is FeedMedia)
|
||||||
|
|
||||||
menu.findItem(R.id.open_feed_item).setVisible(isFeedMedia) // FeedMedia implies it belongs to a Feed
|
menu.findItem(R.id.open_feed_item).setVisible(isFeedMedia) // FeedMedia implies it belongs to a Feed
|
||||||
|
@ -533,22 +221,33 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
|
||||||
menu.findItem(R.id.add_to_favorites_item).setVisible(false)
|
menu.findItem(R.id.add_to_favorites_item).setVisible(false)
|
||||||
menu.findItem(R.id.remove_from_favorites_item).setVisible(false)
|
menu.findItem(R.id.remove_from_favorites_item).setVisible(false)
|
||||||
if (isFeedMedia) {
|
if (isFeedMedia) {
|
||||||
menu.findItem(R.id.add_to_favorites_item).setVisible(!isFavorite)
|
menu.findItem(R.id.add_to_favorites_item).setVisible(!videoEpisodeFragment.isFavorite)
|
||||||
menu.findItem(R.id.remove_from_favorites_item).setVisible(isFavorite)
|
menu.findItem(R.id.remove_from_favorites_item).setVisible(videoEpisodeFragment.isFavorite)
|
||||||
}
|
}
|
||||||
|
|
||||||
menu.findItem(R.id.set_sleeptimer_item).setVisible(!controller!!.sleepTimerActive())
|
menu.findItem(R.id.set_sleeptimer_item).setVisible(!controller.sleepTimerActive())
|
||||||
menu.findItem(R.id.disable_sleeptimer_item).setVisible(controller!!.sleepTimerActive())
|
menu.findItem(R.id.disable_sleeptimer_item).setVisible(controller.sleepTimerActive())
|
||||||
|
|
||||||
menu.findItem(R.id.player_switch_to_audio_only).setVisible(true)
|
menu.findItem(R.id.player_switch_to_audio_only).setVisible(true)
|
||||||
|
|
||||||
menu.findItem(R.id.audio_controls).setVisible(controller!!.audioTracks.size >= 2)
|
menu.findItem(R.id.audio_controls).setVisible(controller.audioTracks.size >= 2)
|
||||||
menu.findItem(R.id.playback_speed).setVisible(true)
|
menu.findItem(R.id.playback_speed).setVisible(true)
|
||||||
menu.findItem(R.id.player_show_chapters).setVisible(true)
|
menu.findItem(R.id.player_show_chapters).setVisible(true)
|
||||||
|
|
||||||
|
if (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)
|
||||||
|
menu.findItem(R.id.disable_sleeptimer_item).setShowAsAction(SHOW_AS_ACTION_NEVER)
|
||||||
|
menu.findItem(R.id.player_switch_to_audio_only).setShowAsAction(SHOW_AS_ACTION_NEVER)
|
||||||
|
menu.findItem(R.id.open_feed_item).setShowAsAction(SHOW_AS_ACTION_NEVER)
|
||||||
|
menu.findItem(R.id.share_item).setShowAsAction(SHOW_AS_ACTION_NEVER)
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
val controller = videoEpisodeFragment.controller
|
||||||
|
|
||||||
// some options option requires FeedItem
|
// some options option requires FeedItem
|
||||||
when {
|
when {
|
||||||
item.itemId == R.id.player_switch_to_audio_only -> {
|
item.itemId == R.id.player_switch_to_audio_only -> {
|
||||||
|
@ -571,17 +270,17 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val media = controller?.getMedia() ?: return false
|
val media = controller.getMedia() ?: return false
|
||||||
val feedItem = getFeedItem(media) // some options option requires FeedItem
|
val feedItem = getFeedItem(media) // some options option requires FeedItem
|
||||||
when {
|
when {
|
||||||
item.itemId == R.id.add_to_favorites_item && feedItem != null -> {
|
item.itemId == R.id.add_to_favorites_item && feedItem != null -> {
|
||||||
DBWriter.addFavoriteItem(feedItem)
|
DBWriter.addFavoriteItem(feedItem)
|
||||||
isFavorite = true
|
videoEpisodeFragment.isFavorite = true
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
item.itemId == R.id.remove_from_favorites_item && feedItem != null -> {
|
item.itemId == R.id.remove_from_favorites_item && feedItem != null -> {
|
||||||
DBWriter.removeFavoriteItem(feedItem)
|
DBWriter.removeFavoriteItem(feedItem)
|
||||||
isFavorite = false
|
videoEpisodeFragment.isFavorite = false
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
item.itemId == R.id.disable_sleeptimer_item || item.itemId == R.id.set_sleeptimer_item -> {
|
item.itemId == R.id.disable_sleeptimer_item || item.itemId == R.id.set_sleeptimer_item -> {
|
||||||
|
@ -615,93 +314,10 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onPositionObserverUpdate() {
|
|
||||||
if (controller == null) return
|
|
||||||
|
|
||||||
val converter = TimeSpeedConverter(controller!!.currentPlaybackSpeedMultiplier)
|
|
||||||
val currentPosition = converter.convert(controller!!.position)
|
|
||||||
val duration = converter.convert(controller!!.duration)
|
|
||||||
val remainingTime = converter.convert(
|
|
||||||
controller!!.duration - controller!!.position)
|
|
||||||
// Log.d(TAG, "currentPosition " + Converter.getDurationStringLong(currentPosition));
|
|
||||||
if (currentPosition == Playable.INVALID_TIME || duration == Playable.INVALID_TIME) {
|
|
||||||
Log.w(TAG, "Could not react to position observer update because of invalid time")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
binding.positionLabel.text = getDurationStringLong(currentPosition)
|
|
||||||
if (showTimeLeft) {
|
|
||||||
binding.durationLabel.text = "-" + getDurationStringLong(remainingTime)
|
|
||||||
} else {
|
|
||||||
binding.durationLabel.text = getDurationStringLong(duration)
|
|
||||||
}
|
|
||||||
updateProgressbarPosition(currentPosition, duration)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateProgressbarPosition(position: Int, duration: Int) {
|
|
||||||
Log.d(TAG, "updateProgressbarPosition($position, $duration)")
|
|
||||||
val progress = (position.toFloat()) / duration
|
|
||||||
binding.sbPosition.progress = (progress * binding.sbPosition.max).toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
|
||||||
if (controller == null) return
|
|
||||||
|
|
||||||
if (fromUser) {
|
|
||||||
prog = progress / (seekBar.max.toFloat())
|
|
||||||
val converter = TimeSpeedConverter(controller!!.currentPlaybackSpeedMultiplier)
|
|
||||||
val position = converter.convert((prog * controller!!.duration).toInt())
|
|
||||||
binding.seekPositionLabel.text = getDurationStringLong(position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStartTrackingTouch(seekBar: SeekBar) {
|
|
||||||
binding.seekCardView.scaleX = .8f
|
|
||||||
binding.seekCardView.scaleY = .8f
|
|
||||||
binding.seekCardView.animate()
|
|
||||||
.setInterpolator(FastOutSlowInInterpolator())
|
|
||||||
.alpha(1f).scaleX(1f).scaleY(1f)
|
|
||||||
.setDuration(200)
|
|
||||||
.start()
|
|
||||||
videoControlsHider.removeCallbacks(hideVideoControls)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStopTrackingTouch(seekBar: SeekBar) {
|
|
||||||
if (controller != null) {
|
|
||||||
controller!!.seekTo((prog * controller!!.duration).toInt())
|
|
||||||
}
|
|
||||||
binding.seekCardView.scaleX = 1f
|
|
||||||
binding.seekCardView.scaleY = 1f
|
|
||||||
binding.seekCardView.animate()
|
|
||||||
.setInterpolator(FastOutSlowInInterpolator())
|
|
||||||
.alpha(0f).scaleX(.8f).scaleY(.8f)
|
|
||||||
.setDuration(200)
|
|
||||||
.start()
|
|
||||||
setupVideoControlsToggler()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkFavorite() {
|
|
||||||
val feedItem = getFeedItem(controller?.getMedia()) ?: return
|
|
||||||
disposable?.dispose()
|
|
||||||
|
|
||||||
disposable = Observable.fromCallable { DBReader.getFeedItem(feedItem.id) }
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(
|
|
||||||
{ item: FeedItem? ->
|
|
||||||
if (item != null) {
|
|
||||||
val isFav = item.isTagged(FeedItem.TAG_FAVORITE)
|
|
||||||
if (isFavorite != isFav) {
|
|
||||||
isFavorite = isFav
|
|
||||||
invalidateOptionsMenu()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun compatEnterPictureInPicture() {
|
private fun compatEnterPictureInPicture() {
|
||||||
if (PictureInPictureUtil.supportsPictureInPicture(this) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
if (PictureInPictureUtil.supportsPictureInPicture(this) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
supportActionBar?.hide()
|
if (videoMode == FULL_SCREEN_VIEW) supportActionBar?.hide()
|
||||||
hideVideoControls(false)
|
videoEpisodeFragment.hideVideoControls(false)
|
||||||
enterPictureInPictureMode()
|
enterPictureInPictureMode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -715,18 +331,18 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
|
||||||
|
|
||||||
when (keyCode) {
|
when (keyCode) {
|
||||||
KeyEvent.KEYCODE_P, KeyEvent.KEYCODE_SPACE -> {
|
KeyEvent.KEYCODE_P, KeyEvent.KEYCODE_SPACE -> {
|
||||||
onPlayPause()
|
videoEpisodeFragment.onPlayPause()
|
||||||
toggleVideoControlsVisibility()
|
videoEpisodeFragment.toggleVideoControlsVisibility()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
KeyEvent.KEYCODE_J, KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_COMMA -> {
|
KeyEvent.KEYCODE_J, KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_COMMA -> {
|
||||||
onRewind()
|
videoEpisodeFragment.onRewind()
|
||||||
showSkipAnimation(false)
|
videoEpisodeFragment.showSkipAnimation(false)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
KeyEvent.KEYCODE_K, KeyEvent.KEYCODE_D, KeyEvent.KEYCODE_PERIOD -> {
|
KeyEvent.KEYCODE_K, KeyEvent.KEYCODE_D, KeyEvent.KEYCODE_PERIOD -> {
|
||||||
onFastForward()
|
videoEpisodeFragment.onFastForward()
|
||||||
showSkipAnimation(true)
|
videoEpisodeFragment.showSkipAnimation(true)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
KeyEvent.KEYCODE_F, KeyEvent.KEYCODE_ESCAPE -> {
|
KeyEvent.KEYCODE_F, KeyEvent.KEYCODE_ESCAPE -> {
|
||||||
|
@ -756,7 +372,8 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
|
||||||
}
|
}
|
||||||
//Go to x% of video:
|
//Go to x% of video:
|
||||||
if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
|
if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
|
||||||
controller?.seekTo((0.1f * (keyCode - KeyEvent.KEYCODE_0) * controller!!.duration).toInt())
|
val controller = videoEpisodeFragment.controller
|
||||||
|
controller?.seekTo((0.1f * (keyCode - KeyEvent.KEYCODE_0) * controller.duration).toInt())
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return super.onKeyUp(keyCode, event)
|
return super.onKeyUp(keyCode, event)
|
||||||
|
@ -764,23 +381,26 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "VideoplayerActivity"
|
private const val TAG = "VideoplayerActivity"
|
||||||
|
const val WINDOW_VIEW = 1
|
||||||
|
const val FULL_SCREEN_VIEW = 2
|
||||||
|
const val AUDIO_ONLY = 3
|
||||||
|
|
||||||
private fun getWebsiteLinkWithFallback(media: Playable?): String? {
|
private fun getWebsiteLinkWithFallback(media: Playable?): String? {
|
||||||
when {
|
return when {
|
||||||
media == null -> {
|
media == null -> {
|
||||||
return null
|
null
|
||||||
}
|
}
|
||||||
!media.getWebsiteLink().isNullOrBlank() -> {
|
!media.getWebsiteLink().isNullOrBlank() -> {
|
||||||
return media.getWebsiteLink()
|
media.getWebsiteLink()
|
||||||
}
|
}
|
||||||
media is FeedMedia -> {
|
media is FeedMedia -> {
|
||||||
return getLinkWithFallback(media.item)
|
getLinkWithFallback(media.item)
|
||||||
}
|
}
|
||||||
else -> return null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getFeedItem(playable: Playable?): FeedItem? {
|
fun getFeedItem(playable: Playable?): FeedItem? {
|
||||||
return if (playable is FeedMedia) {
|
return if (playable is FeedMedia) {
|
||||||
playable.item
|
playable.item
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -104,9 +104,6 @@ open class VariableSpeedDialog : BottomSheetDialogFragment() {
|
||||||
binding.global.isChecked = true
|
binding.global.isChecked = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if (!settingCode[0]) binding.currentAudio.visibility = View.INVISIBLE
|
|
||||||
// if (!settingCode[1]) binding.currentPodcast.visibility = View.INVISIBLE
|
|
||||||
// if (!settingCode[2]) binding.global.visibility = View.INVISIBLE
|
|
||||||
|
|
||||||
speedSeekBar = binding.speedSeekBar
|
speedSeekBar = binding.speedSeekBar
|
||||||
speedSeekBar.setProgressChangedListener { multiplier: Float ->
|
speedSeekBar.setProgressChangedListener { multiplier: Float ->
|
||||||
|
@ -184,9 +181,11 @@ open class VariableSpeedDialog : BottomSheetDialogFragment() {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
holder.chip.setOnClickListener { Handler(Looper.getMainLooper()).postDelayed({
|
holder.chip.setOnClickListener { Handler(Looper.getMainLooper()).postDelayed({
|
||||||
if (binding.currentAudio.isChecked) settingCode[0] = true
|
Log.d("VariableSpeedDialog", "holder.chip settingCode0: ${settingCode[0]} ${settingCode[1]} ${settingCode[2]}")
|
||||||
if (binding.currentPodcast.isChecked) settingCode[1] = true
|
settingCode[0] = binding.currentAudio.isChecked
|
||||||
if (binding.global.isChecked) settingCode[2] = true
|
settingCode[1] = binding.currentPodcast.isChecked
|
||||||
|
settingCode[2] = binding.global.isChecked
|
||||||
|
Log.d("VariableSpeedDialog", "holder.chip settingCode: ${settingCode[0]} ${settingCode[1]} ${settingCode[2]}")
|
||||||
|
|
||||||
if (controller != null) {
|
if (controller != null) {
|
||||||
dismiss()
|
dismiss()
|
||||||
|
@ -203,13 +202,17 @@ open class VariableSpeedDialog : BottomSheetDialogFragment() {
|
||||||
return selectedSpeeds[position].hashCode().toLong()
|
return selectedSpeeds[position].hashCode().toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ViewHolder internal constructor(var chip: Chip) : RecyclerView.ViewHolder(
|
inner class ViewHolder internal constructor(var chip: Chip) : RecyclerView.ViewHolder(chip)
|
||||||
chip)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
/**
|
||||||
|
* @param settingCode_ array at input indicate which categories can be set, at output which categories are changed
|
||||||
|
* @param index_default indicates which category is checked by default
|
||||||
|
*/
|
||||||
fun newInstance(settingCode_: BooleanArray? = null, index_default: Int? = null): VariableSpeedDialog? {
|
fun newInstance(settingCode_: BooleanArray? = null, index_default: Int? = null): VariableSpeedDialog? {
|
||||||
val settingCode = settingCode_ ?: BooleanArray(3){false}
|
val settingCode = settingCode_ ?: BooleanArray(3){true}
|
||||||
|
Log.d("VariableSpeedDialog", "newInstance settingCode: ${settingCode[0]} ${settingCode[1]} ${settingCode[2]}")
|
||||||
if (settingCode.size != 3) {
|
if (settingCode.size != 3) {
|
||||||
Log.e("VariableSpeedDialog", "wrong settingCode dimension")
|
Log.e("VariableSpeedDialog", "wrong settingCode dimension")
|
||||||
return null
|
return null
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package ac.mdiq.podcini.ui.dialog
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import ac.mdiq.podcini.R
|
||||||
|
import ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent
|
||||||
|
import ac.mdiq.podcini.preferences.UserPreferences.feedOrder
|
||||||
|
import ac.mdiq.podcini.preferences.UserPreferences.setFeedOrder
|
||||||
|
import ac.mdiq.podcini.preferences.UserPreferences.setVideoMode
|
||||||
|
import ac.mdiq.podcini.preferences.UserPreferences.videoPlayMode
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
|
||||||
|
object VideoModeDialog {
|
||||||
|
fun showDialog(context: Context) {
|
||||||
|
val dialog = MaterialAlertDialogBuilder(context)
|
||||||
|
dialog.setTitle(context.getString(R.string.pref_playback_video_mode))
|
||||||
|
dialog.setNegativeButton(android.R.string.cancel) { d: DialogInterface, _: Int -> d.dismiss() }
|
||||||
|
|
||||||
|
val selected = videoPlayMode
|
||||||
|
val entryValues = listOf(*context.resources.getStringArray(R.array.video_mode_options_values))
|
||||||
|
val selectedIndex = entryValues.indexOf("" + selected)
|
||||||
|
|
||||||
|
val items = context.resources.getStringArray(R.array.video_mode_options)
|
||||||
|
dialog.setSingleChoiceItems(items, selectedIndex) { d: DialogInterface, which: Int ->
|
||||||
|
if (selectedIndex != which) {
|
||||||
|
setVideoMode(entryValues[which].toInt())
|
||||||
|
}
|
||||||
|
d.dismiss()
|
||||||
|
}
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,11 +6,14 @@ import ac.mdiq.podcini.databinding.InternalPlayerFragmentBinding
|
||||||
import ac.mdiq.podcini.feed.util.ImageResourceUtils
|
import ac.mdiq.podcini.feed.util.ImageResourceUtils
|
||||||
import ac.mdiq.podcini.feed.util.PlaybackSpeedUtils
|
import ac.mdiq.podcini.feed.util.PlaybackSpeedUtils
|
||||||
import ac.mdiq.podcini.playback.PlaybackController
|
import ac.mdiq.podcini.playback.PlaybackController
|
||||||
|
import ac.mdiq.podcini.playback.PlaybackController.Companion
|
||||||
|
import ac.mdiq.podcini.playback.PlaybackServiceStarter
|
||||||
import ac.mdiq.podcini.playback.base.PlayerStatus
|
import ac.mdiq.podcini.playback.base.PlayerStatus
|
||||||
import ac.mdiq.podcini.playback.cast.CastEnabledActivity
|
import ac.mdiq.podcini.playback.cast.CastEnabledActivity
|
||||||
import ac.mdiq.podcini.preferences.UserPreferences
|
import ac.mdiq.podcini.preferences.UserPreferences
|
||||||
import ac.mdiq.podcini.receiver.MediaButtonReceiver
|
import ac.mdiq.podcini.receiver.MediaButtonReceiver
|
||||||
import ac.mdiq.podcini.playback.service.PlaybackService
|
import ac.mdiq.podcini.playback.service.PlaybackService
|
||||||
|
import ac.mdiq.podcini.preferences.UserPreferences.videoPlayMode
|
||||||
import ac.mdiq.podcini.storage.model.feed.Chapter
|
import ac.mdiq.podcini.storage.model.feed.Chapter
|
||||||
import ac.mdiq.podcini.storage.model.feed.FeedItem
|
import ac.mdiq.podcini.storage.model.feed.FeedItem
|
||||||
import ac.mdiq.podcini.storage.model.feed.FeedMedia
|
import ac.mdiq.podcini.storage.model.feed.FeedMedia
|
||||||
|
@ -23,6 +26,7 @@ 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.actions.menuhandler.FeedItemMenuHandler
|
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.ChapterSeekBar
|
||||||
import ac.mdiq.podcini.ui.view.PlayButton
|
import ac.mdiq.podcini.ui.view.PlayButton
|
||||||
import ac.mdiq.podcini.util.ChapterUtils
|
import ac.mdiq.podcini.util.ChapterUtils
|
||||||
|
@ -97,7 +101,6 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
||||||
private var currentMedia: Playable? = null
|
private var currentMedia: Playable? = null
|
||||||
private var currentitem: FeedItem? = null
|
private var currentitem: FeedItem? = null
|
||||||
|
|
||||||
@SuppressLint("WrongConstant")
|
|
||||||
override fun onCreateView(inflater: LayoutInflater,
|
override fun onCreateView(inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
|
@ -447,6 +450,11 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun scrollToTop() {
|
||||||
|
itemDescFrag.scrollToTop()
|
||||||
|
}
|
||||||
|
|
||||||
fun fadePlayerToToolbar(slideOffset: Float) {
|
fun fadePlayerToToolbar(slideOffset: Float) {
|
||||||
val playerFadeProgress = (max(0.0, min(0.2, (slideOffset - 0.2f).toDouble())) / 0.2f).toFloat()
|
val playerFadeProgress = (max(0.0, min(0.2, (slideOffset - 0.2f).toDouble())) / 0.2f).toFloat()
|
||||||
val player = playerView1
|
val player = playerView1
|
||||||
|
@ -515,9 +523,12 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
||||||
Log.d(TAG, "internalPlayerFragment was clicked")
|
Log.d(TAG, "internalPlayerFragment was clicked")
|
||||||
val media = controller?.getMedia()
|
val media = controller?.getMedia()
|
||||||
if (media != null) {
|
if (media != null) {
|
||||||
if (media.getMediaType() == MediaType.AUDIO) {
|
if (media.getMediaType() == MediaType.AUDIO || videoPlayMode == AUDIO_ONLY) {
|
||||||
|
controller!!.ensureService()
|
||||||
(activity as MainActivity).bottomSheet.setState(BottomSheetBehavior.STATE_EXPANDED)
|
(activity as MainActivity).bottomSheet.setState(BottomSheetBehavior.STATE_EXPANDED)
|
||||||
} else {
|
} else {
|
||||||
|
controller?.playPause()
|
||||||
|
// controller!!.ensureService()
|
||||||
val intent = PlaybackService.getPlayerActivityIntent(requireContext(), media)
|
val intent = PlaybackService.getPlayerActivityIntent(requireContext(), media)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
|
@ -712,19 +723,17 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
||||||
.fitCenter()
|
.fitCenter()
|
||||||
.dontAnimate()
|
.dontAnimate()
|
||||||
|
|
||||||
val imgLoc = ImageResourceUtils.getEpisodeListImageLocation(media)
|
val imgLoc = ImageResourceUtils.getEpisodeListImageLocation(media) + "sdfsdf"
|
||||||
val imgLocFB = ImageResourceUtils.getFallbackImageLocation(media)
|
val imgLocFB = ImageResourceUtils.getFallbackImageLocation(media)
|
||||||
when {
|
|
||||||
!imgLoc.isNullOrBlank() -> Glide.with(this)
|
Glide.with(this)
|
||||||
.load(imgLoc)
|
.load(imgLoc)
|
||||||
.apply(options)
|
.error(Glide.with(this)
|
||||||
.into(imgvCover)
|
|
||||||
!imgLocFB.isNullOrBlank() -> Glide.with(this)
|
|
||||||
.load(imgLocFB)
|
.load(imgLocFB)
|
||||||
|
.error(R.mipmap.ic_launcher)
|
||||||
|
.apply(options))
|
||||||
.apply(options)
|
.apply(options)
|
||||||
.into(imgvCover)
|
.into(imgvCover)
|
||||||
else -> imgvCover.setImageResource(R.mipmap.ic_launcher)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (controller?.isPlayingVideoLocally == true) {
|
if (controller?.isPlayingVideoLocally == true) {
|
||||||
(activity as MainActivity).bottomSheet.setLocked(true)
|
(activity as MainActivity).bottomSheet.setLocked(true)
|
||||||
|
@ -747,7 +756,6 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG: String = "AudioPlayerFragment"
|
const val TAG: String = "AudioPlayerFragment"
|
||||||
}
|
}
|
||||||
|
|
|
@ -316,7 +316,6 @@ class PlayerDetailsFragment : Fragment() {
|
||||||
|
|
||||||
@UnstableApi private fun seekToPrevChapter() {
|
@UnstableApi private fun seekToPrevChapter() {
|
||||||
val curr: Chapter? = currentChapter
|
val curr: Chapter? = currentChapter
|
||||||
|
|
||||||
if (controller == null || curr == null || displayedChapterIndex == -1) return
|
if (controller == null || curr == null || displayedChapterIndex == -1) return
|
||||||
|
|
||||||
when {
|
when {
|
||||||
|
@ -353,8 +352,8 @@ class PlayerDetailsFragment : Fragment() {
|
||||||
val prefs = requireActivity().getSharedPreferences(PREF, Activity.MODE_PRIVATE)
|
val prefs = requireActivity().getSharedPreferences(PREF, Activity.MODE_PRIVATE)
|
||||||
val editor = prefs.edit()
|
val editor = prefs.edit()
|
||||||
if (controller?.getMedia() != null) {
|
if (controller?.getMedia() != null) {
|
||||||
Log.d(TAG, "Saving scroll position: " + webvDescription.scrollY)
|
Log.d(TAG, "Saving scroll position: " + binding.itemDescriptionFragment.scrollY)
|
||||||
editor.putInt(PREF_SCROLL_Y, webvDescription.scrollY)
|
editor.putInt(PREF_SCROLL_Y, binding.itemDescriptionFragment.scrollY)
|
||||||
editor.putString(PREF_PLAYABLE_ID, controller!!.getMedia()!!.getIdentifier().toString())
|
editor.putString(PREF_PLAYABLE_ID, controller!!.getMedia()!!.getIdentifier().toString())
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "savePreferences was called while media or webview was null")
|
Log.d(TAG, "savePreferences was called while media or webview was null")
|
||||||
|
@ -374,11 +373,12 @@ class PlayerDetailsFragment : Fragment() {
|
||||||
if (scrollY != -1) {
|
if (scrollY != -1) {
|
||||||
if (id == controller?.getMedia()?.getIdentifier()?.toString()) {
|
if (id == controller?.getMedia()?.getIdentifier()?.toString()) {
|
||||||
Log.d(TAG, "Restored scroll Position: $scrollY")
|
Log.d(TAG, "Restored scroll Position: $scrollY")
|
||||||
webvDescription.scrollTo(webvDescription.scrollX, scrollY)
|
binding.itemDescriptionFragment.scrollTo(binding.itemDescriptionFragment.scrollX, scrollY)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
Log.d(TAG, "reset scroll Position: 0")
|
Log.d(TAG, "reset scroll Position: 0")
|
||||||
webvDescription.scrollTo(webvDescription.scrollX, 0)
|
binding.itemDescriptionFragment.scrollTo(0, 0)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -386,7 +386,7 @@ class PlayerDetailsFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun scrollToTop() {
|
fun scrollToTop() {
|
||||||
webvDescription.scrollTo(0, 0)
|
binding.itemDescriptionFragment.scrollTo(0, 0)
|
||||||
savePreference()
|
savePreference()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,571 @@
|
||||||
|
package ac.mdiq.podcini.ui.fragment
|
||||||
|
|
||||||
|
import ac.mdiq.podcini.R
|
||||||
|
import ac.mdiq.podcini.databinding.VideoEpisodeFragmentBinding
|
||||||
|
import ac.mdiq.podcini.playback.PlaybackController
|
||||||
|
import ac.mdiq.podcini.playback.base.PlayerStatus
|
||||||
|
import ac.mdiq.podcini.preferences.UserPreferences.fastForwardSecs
|
||||||
|
import ac.mdiq.podcini.preferences.UserPreferences.rewindSecs
|
||||||
|
import ac.mdiq.podcini.preferences.UserPreferences.setShowRemainTimeSetting
|
||||||
|
import ac.mdiq.podcini.preferences.UserPreferences.shouldShowRemainingTime
|
||||||
|
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.appstartintent.MainActivityStarter
|
||||||
|
import ac.mdiq.podcini.ui.dialog.SkipPreferenceDialog
|
||||||
|
import ac.mdiq.podcini.ui.utils.PictureInPictureUtil
|
||||||
|
import ac.mdiq.podcini.ui.utils.ShownotesCleaner
|
||||||
|
import ac.mdiq.podcini.ui.view.ShownotesWebView
|
||||||
|
import ac.mdiq.podcini.util.Converter.getDurationStringLong
|
||||||
|
import ac.mdiq.podcini.util.TimeSpeedConverter
|
||||||
|
import ac.mdiq.podcini.util.event.playback.BufferUpdateEvent
|
||||||
|
import ac.mdiq.podcini.util.event.playback.PlaybackPositionEvent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.*
|
||||||
|
import android.view.animation.*
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import android.widget.SeekBar
|
||||||
|
import android.widget.SeekBar.OnSeekBarChangeListener
|
||||||
|
import androidx.annotation.OptIn
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.app.ActivityCompat.invalidateOptionsMenu
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import io.reactivex.Observable
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.disposables.Disposable
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import org.greenrobot.eventbus.Subscribe
|
||||||
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener {
|
||||||
|
private var _binding: VideoEpisodeFragmentBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
private lateinit var root: ViewGroup
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if video controls are currently visible.
|
||||||
|
*/
|
||||||
|
private var videoControlsShowing = true
|
||||||
|
private var videoSurfaceCreated = false
|
||||||
|
private var lastScreenTap: Long = 0
|
||||||
|
private val videoControlsHider = Handler(Looper.getMainLooper())
|
||||||
|
private var showTimeLeft = false
|
||||||
|
private var disposable: Disposable? = null
|
||||||
|
private var prog = 0f
|
||||||
|
|
||||||
|
private var itemsLoaded = false
|
||||||
|
private var item: FeedItem? = null
|
||||||
|
private var webviewData: String? = null
|
||||||
|
private lateinit var webvDescription: ShownotesWebView
|
||||||
|
|
||||||
|
var destroyingDueToReload = false
|
||||||
|
var controller: PlaybackController? = null
|
||||||
|
var isFavorite = false
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class) override fun onCreateView(inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?): View {
|
||||||
|
super.onCreateView(inflater, container, savedInstanceState)
|
||||||
|
_binding = VideoEpisodeFragmentBinding.inflate(LayoutInflater.from(requireContext()))
|
||||||
|
root = binding.root
|
||||||
|
|
||||||
|
controller = newPlaybackController()
|
||||||
|
controller!!.init()
|
||||||
|
// loadMediaInfo()
|
||||||
|
|
||||||
|
setupView()
|
||||||
|
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class) private fun newPlaybackController(): PlaybackController {
|
||||||
|
return object : PlaybackController(requireActivity()) {
|
||||||
|
override fun updatePlayButtonShowsPlay(showPlay: Boolean) {
|
||||||
|
Log.d(TAG, "updatePlayButtonShowsPlay called")
|
||||||
|
binding.playButton.setIsShowPlay(showPlay)
|
||||||
|
if (showPlay) {
|
||||||
|
(activity as AppCompatActivity).window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
} else {
|
||||||
|
(activity as AppCompatActivity).window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
setupVideoAspectRatio()
|
||||||
|
if (videoSurfaceCreated && controller != null) {
|
||||||
|
Log.d(TAG, "Videosurface already created, setting videosurface now")
|
||||||
|
controller!!.setVideoSurface(binding.videoView.holder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadMediaInfo() {
|
||||||
|
this@VideoEpisodeFragment.loadMediaInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPlaybackEnd() {
|
||||||
|
activity?.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
onPositionObserverUpdate()
|
||||||
|
EventBus.getDefault().register(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
override fun onPause() {
|
||||||
|
if (!PictureInPictureUtil.isInPictureInPictureMode(requireActivity())) {
|
||||||
|
if (controller?.status == PlayerStatus.PLAYING) {
|
||||||
|
controller!!.pause()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.onPause()
|
||||||
|
}
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
override fun onStop() {
|
||||||
|
EventBus.getDefault().unregister(this)
|
||||||
|
super.onStop()
|
||||||
|
if (!PictureInPictureUtil.isInPictureInPictureMode(requireActivity())) {
|
||||||
|
videoControlsHider.removeCallbacks(hideVideoControls)
|
||||||
|
}
|
||||||
|
// Controller released; we will not receive buffering updates
|
||||||
|
binding.progressBar.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
root.removeView(webvDescription)
|
||||||
|
webvDescription.destroy()
|
||||||
|
_binding = null
|
||||||
|
controller?.release()
|
||||||
|
controller = null // prevent leak
|
||||||
|
disposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
|
@Suppress("unused")
|
||||||
|
fun bufferUpdate(event: BufferUpdateEvent) {
|
||||||
|
when {
|
||||||
|
event.hasStarted() -> {
|
||||||
|
binding.progressBar.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
event.hasEnded() -> {
|
||||||
|
binding.progressBar.visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
binding.sbPosition.secondaryProgress = (event.progress * binding.sbPosition.max).toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupVideoAspectRatio() {
|
||||||
|
if (videoSurfaceCreated && controller != null) {
|
||||||
|
val videoSize = controller!!.videoSize
|
||||||
|
if (videoSize != null && videoSize.first > 0 && videoSize.second > 0) {
|
||||||
|
Log.d(TAG, "Width,height of video: " + videoSize.first + ", " + videoSize.second)
|
||||||
|
val videoWidth = resources.displayMetrics.widthPixels
|
||||||
|
val videoHeight = (videoWidth.toFloat() / videoSize.first * videoSize.second).toInt()
|
||||||
|
Log.d(TAG, "Width,height of video: " + videoWidth + ", " + videoHeight)
|
||||||
|
binding.videoView.setVideoSize(videoWidth, videoHeight)
|
||||||
|
// binding.videoView.setVideoSize(videoSize.first, videoSize.second)
|
||||||
|
// binding.videoView.setVideoSize(-1, -1)
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Could not determine video size")
|
||||||
|
val videoWidth = resources.displayMetrics.widthPixels
|
||||||
|
val videoHeight = (videoWidth.toFloat() / 16 * 9).toInt()
|
||||||
|
Log.d(TAG, "Width,height of video: " + videoWidth + ", " + videoHeight)
|
||||||
|
binding.videoView.setVideoSize(videoWidth, videoHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class) private fun loadMediaInfo() {
|
||||||
|
Log.d(TAG, "loadMediaInfo called")
|
||||||
|
if (controller?.getMedia() == null) return
|
||||||
|
|
||||||
|
if (controller!!.status == PlayerStatus.PLAYING && !controller!!.isPlayingVideoLocally) {
|
||||||
|
Log.d(TAG, "Closing, no longer video")
|
||||||
|
destroyingDueToReload = true
|
||||||
|
activity?.finish()
|
||||||
|
MainActivityStarter(requireContext()).withOpenPlayer().start()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
showTimeLeft = shouldShowRemainingTime()
|
||||||
|
onPositionObserverUpdate()
|
||||||
|
load()
|
||||||
|
val media = controller!!.getMedia()
|
||||||
|
if (media != null) {
|
||||||
|
(activity as AppCompatActivity).supportActionBar!!.subtitle = media.getEpisodeTitle()
|
||||||
|
(activity as AppCompatActivity).supportActionBar!!.title = media.getFeedTitle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@UnstableApi private fun load() {
|
||||||
|
disposable?.dispose()
|
||||||
|
Log.d(TAG, "load() called")
|
||||||
|
|
||||||
|
disposable = Observable.fromCallable<FeedItem?> { this.loadInBackground() }
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe({ result: FeedItem? ->
|
||||||
|
item = result
|
||||||
|
Log.d(TAG, "load() item ${item?.id}")
|
||||||
|
if (item != null) {
|
||||||
|
val isFav = item!!.isTagged(FeedItem.TAG_FAVORITE)
|
||||||
|
if (isFavorite != isFav) {
|
||||||
|
isFavorite = isFav
|
||||||
|
invalidateOptionsMenu(requireActivity())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onFragmentLoaded()
|
||||||
|
itemsLoaded = true
|
||||||
|
}, { error: Throwable? ->
|
||||||
|
Log.e(TAG, Log.getStackTraceString(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadInBackground(): FeedItem? {
|
||||||
|
val feedItem = VideoplayerActivity.getFeedItem(controller?.getMedia())
|
||||||
|
if (feedItem != null) {
|
||||||
|
val duration = feedItem.media?.getDuration()?: Int.MAX_VALUE
|
||||||
|
DBReader.loadDescriptionOfFeedItem(feedItem)
|
||||||
|
webviewData = ShownotesCleaner(requireContext(), feedItem.description?:"", duration).processShownotes()
|
||||||
|
}
|
||||||
|
return feedItem
|
||||||
|
}
|
||||||
|
|
||||||
|
@UnstableApi private fun onFragmentLoaded() {
|
||||||
|
if (webviewData != null && !itemsLoaded) {
|
||||||
|
webvDescription.loadDataWithBaseURL("https://127.0.0.1", webviewData!!, "text/html", "utf-8", "about:blank")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
private fun setupView() {
|
||||||
|
showTimeLeft = shouldShowRemainingTime()
|
||||||
|
Log.d(TAG, "setupView showTimeLeft: $showTimeLeft")
|
||||||
|
|
||||||
|
binding.durationLabel.setOnClickListener {
|
||||||
|
showTimeLeft = !showTimeLeft
|
||||||
|
val media = controller?.getMedia() ?: return@setOnClickListener
|
||||||
|
|
||||||
|
val converter = TimeSpeedConverter(controller!!.currentPlaybackSpeedMultiplier)
|
||||||
|
val length: String
|
||||||
|
if (showTimeLeft) {
|
||||||
|
val remainingTime = converter.convert(media.getDuration() - media.getPosition())
|
||||||
|
length = "-" + getDurationStringLong(remainingTime)
|
||||||
|
} else {
|
||||||
|
val duration = converter.convert(media.getDuration())
|
||||||
|
length = getDurationStringLong(duration)
|
||||||
|
}
|
||||||
|
binding.durationLabel.text = length
|
||||||
|
|
||||||
|
setShowRemainTimeSetting(showTimeLeft)
|
||||||
|
Log.d("timeleft on click", if (showTimeLeft) "true" else "false")
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.sbPosition.setOnSeekBarChangeListener(this)
|
||||||
|
binding.rewindButton.setOnClickListener { onRewind() }
|
||||||
|
binding.rewindButton.setOnLongClickListener {
|
||||||
|
SkipPreferenceDialog.showSkipPreference(requireContext(),
|
||||||
|
SkipPreferenceDialog.SkipDirection.SKIP_REWIND, null)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
binding.playButton.setIsVideoScreen(true)
|
||||||
|
binding.playButton.setOnClickListener { onPlayPause() }
|
||||||
|
binding.fastForwardButton.setOnClickListener { onFastForward() }
|
||||||
|
binding.fastForwardButton.setOnLongClickListener {
|
||||||
|
SkipPreferenceDialog.showSkipPreference(requireContext(),
|
||||||
|
SkipPreferenceDialog.SkipDirection.SKIP_FORWARD, null)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
// To suppress touches directly below the slider
|
||||||
|
binding.bottomControlsContainer.setOnTouchListener { _: View?, _: MotionEvent? -> true }
|
||||||
|
binding.videoView.holder.addCallback(surfaceHolderCallback)
|
||||||
|
binding.bottomControlsContainer.fitsSystemWindows = true
|
||||||
|
// binding.videoView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||||
|
|
||||||
|
setupVideoControlsToggler()
|
||||||
|
// (activity as AppCompatActivity).window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
||||||
|
|
||||||
|
binding.videoPlayerContainer.setOnTouchListener(onVideoviewTouched)
|
||||||
|
binding.videoPlayerContainer.viewTreeObserver.addOnGlobalLayoutListener {
|
||||||
|
binding.videoView.setAvailableSize(
|
||||||
|
binding.videoPlayerContainer.width.toFloat(), binding.videoPlayerContainer.height.toFloat())
|
||||||
|
}
|
||||||
|
|
||||||
|
webvDescription = binding.webvDescription
|
||||||
|
// webvDescription.setTimecodeSelectedListener { time: Int? ->
|
||||||
|
// val cMedia = controller?.getMedia()
|
||||||
|
// if (item?.media?.getIdentifier() == cMedia?.getIdentifier()) {
|
||||||
|
// controller?.seekTo(time ?: 0)
|
||||||
|
// } else {
|
||||||
|
// (activity as MainActivity).showSnackbarAbovePlayer(R.string.play_this_to_seek_position,
|
||||||
|
// Snackbar.LENGTH_LONG)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// registerForContextMenu(webvDescription)
|
||||||
|
// webvDescription.visibility = View.GONE
|
||||||
|
|
||||||
|
binding.toggleViews.setOnClickListener {
|
||||||
|
(activity as? VideoplayerActivity)?.toggleViews()
|
||||||
|
}
|
||||||
|
binding.audioOnly.setOnClickListener {
|
||||||
|
(activity as? VideoplayerActivity)?.switchToAudioOnly = true
|
||||||
|
(activity as? VideoplayerActivity)?.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private val onVideoviewTouched = View.OnTouchListener { v: View, event: MotionEvent ->
|
||||||
|
if (event.action != MotionEvent.ACTION_DOWN) return@OnTouchListener false
|
||||||
|
|
||||||
|
if (PictureInPictureUtil.isInPictureInPictureMode(requireActivity())) return@OnTouchListener true
|
||||||
|
|
||||||
|
videoControlsHider.removeCallbacks(hideVideoControls)
|
||||||
|
|
||||||
|
if (System.currentTimeMillis() - lastScreenTap < 300) {
|
||||||
|
if (event.x > v.measuredWidth / 2.0f) {
|
||||||
|
onFastForward()
|
||||||
|
showSkipAnimation(true)
|
||||||
|
} else {
|
||||||
|
onRewind()
|
||||||
|
showSkipAnimation(false)
|
||||||
|
}
|
||||||
|
if (videoControlsShowing) {
|
||||||
|
hideVideoControls(false)
|
||||||
|
if ((activity as VideoplayerActivity).videoMode == VideoplayerActivity.FULL_SCREEN_VIEW)
|
||||||
|
(activity as AppCompatActivity).supportActionBar?.hide()
|
||||||
|
videoControlsShowing = false
|
||||||
|
}
|
||||||
|
return@OnTouchListener true
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleVideoControlsVisibility()
|
||||||
|
if (videoControlsShowing) setupVideoControlsToggler()
|
||||||
|
|
||||||
|
lastScreenTap = System.currentTimeMillis()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleVideoControlsVisibility() {
|
||||||
|
if (videoControlsShowing) {
|
||||||
|
hideVideoControls(true)
|
||||||
|
if ((activity as VideoplayerActivity).videoMode == VideoplayerActivity.FULL_SCREEN_VIEW)
|
||||||
|
(activity as AppCompatActivity).supportActionBar?.hide()
|
||||||
|
} else {
|
||||||
|
showVideoControls()
|
||||||
|
(activity as AppCompatActivity).supportActionBar?.show()
|
||||||
|
}
|
||||||
|
videoControlsShowing = !videoControlsShowing
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showSkipAnimation(isForward: Boolean) {
|
||||||
|
val skipAnimation = AnimationSet(true)
|
||||||
|
skipAnimation.addAnimation(ScaleAnimation(1f, 2f, 1f, 2f,
|
||||||
|
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f))
|
||||||
|
skipAnimation.addAnimation(AlphaAnimation(1f, 0f))
|
||||||
|
skipAnimation.fillAfter = false
|
||||||
|
skipAnimation.duration = 800
|
||||||
|
|
||||||
|
val params = binding.skipAnimationImage.layoutParams as FrameLayout.LayoutParams
|
||||||
|
if (isForward) {
|
||||||
|
binding.skipAnimationImage.setImageResource(R.drawable.ic_fast_forward_video_white)
|
||||||
|
params.gravity = Gravity.RIGHT or Gravity.CENTER_VERTICAL
|
||||||
|
} else {
|
||||||
|
binding.skipAnimationImage.setImageResource(R.drawable.ic_fast_rewind_video_white)
|
||||||
|
params.gravity = Gravity.LEFT or Gravity.CENTER_VERTICAL
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.skipAnimationImage.visibility = View.VISIBLE
|
||||||
|
binding.skipAnimationImage.layoutParams = params
|
||||||
|
binding.skipAnimationImage.startAnimation(skipAnimation)
|
||||||
|
skipAnimation.setAnimationListener(object : Animation.AnimationListener {
|
||||||
|
override fun onAnimationStart(animation: Animation) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAnimationEnd(animation: Animation) {
|
||||||
|
binding.skipAnimationImage.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAnimationRepeat(animation: Animation) {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private val surfaceHolderCallback: SurfaceHolder.Callback = object : SurfaceHolder.Callback {
|
||||||
|
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
|
||||||
|
holder.setFixedSize(width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
override fun surfaceCreated(holder: SurfaceHolder) {
|
||||||
|
Log.d(TAG, "Videoview holder created")
|
||||||
|
videoSurfaceCreated = true
|
||||||
|
if (controller?.status == PlayerStatus.PLAYING) {
|
||||||
|
controller!!.setVideoSurface(holder)
|
||||||
|
}
|
||||||
|
setupVideoAspectRatio()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||||
|
Log.d(TAG, "Videosurface was destroyed")
|
||||||
|
videoSurfaceCreated = false
|
||||||
|
if (controller != null && !destroyingDueToReload && !(activity as VideoplayerActivity).switchToAudioOnly) {
|
||||||
|
controller!!.notifyVideoSurfaceAbandoned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
fun onRewind() {
|
||||||
|
if (controller == null) return
|
||||||
|
|
||||||
|
val curr = controller!!.position
|
||||||
|
controller!!.seekTo(curr - rewindSecs * 1000)
|
||||||
|
setupVideoControlsToggler()
|
||||||
|
}
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
fun onPlayPause() {
|
||||||
|
if (controller == null) return
|
||||||
|
|
||||||
|
controller!!.playPause()
|
||||||
|
setupVideoControlsToggler()
|
||||||
|
}
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
fun onFastForward() {
|
||||||
|
if (controller == null) return
|
||||||
|
|
||||||
|
val curr = controller!!.position
|
||||||
|
controller!!.seekTo(curr + fastForwardSecs * 1000)
|
||||||
|
setupVideoControlsToggler()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupVideoControlsToggler() {
|
||||||
|
videoControlsHider.removeCallbacks(hideVideoControls)
|
||||||
|
videoControlsHider.postDelayed(hideVideoControls, 2500)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val hideVideoControls = Runnable {
|
||||||
|
if (videoControlsShowing) {
|
||||||
|
Log.d(TAG, "Hiding video controls")
|
||||||
|
hideVideoControls(true)
|
||||||
|
if ((activity as VideoplayerActivity).videoMode == VideoplayerActivity.FULL_SCREEN_VIEW)
|
||||||
|
(activity as? AppCompatActivity)?.supportActionBar?.hide()
|
||||||
|
videoControlsShowing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showVideoControls() {
|
||||||
|
binding.bottomControlsContainer.visibility = View.VISIBLE
|
||||||
|
binding.controlsContainer.visibility = View.VISIBLE
|
||||||
|
val animation = AnimationUtils.loadAnimation(requireContext(), R.anim.fade_in)
|
||||||
|
if (animation != null) {
|
||||||
|
binding.bottomControlsContainer.startAnimation(animation)
|
||||||
|
binding.controlsContainer.startAnimation(animation)
|
||||||
|
}
|
||||||
|
(activity as AppCompatActivity).window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
|
||||||
|
// binding.videoView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE
|
||||||
|
binding.bottomControlsContainer.fitsSystemWindows = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hideVideoControls(showAnimation: Boolean) {
|
||||||
|
if (showAnimation) {
|
||||||
|
val animation = AnimationUtils.loadAnimation(requireContext(), R.anim.fade_out)
|
||||||
|
if (animation != null) {
|
||||||
|
binding.bottomControlsContainer.startAnimation(animation)
|
||||||
|
binding.controlsContainer.startAnimation(animation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(activity as AppCompatActivity).window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
|
||||||
|
// (activity as AppCompatActivity).window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LOW_PROFILE
|
||||||
|
// or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||||
|
// or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)
|
||||||
|
binding.bottomControlsContainer.fitsSystemWindows = true
|
||||||
|
|
||||||
|
binding.bottomControlsContainer.visibility = View.GONE
|
||||||
|
binding.controlsContainer.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
|
fun onEventMainThread(event: PlaybackPositionEvent?) {
|
||||||
|
onPositionObserverUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onPositionObserverUpdate() {
|
||||||
|
if (controller == null) return
|
||||||
|
|
||||||
|
val converter = TimeSpeedConverter(controller!!.currentPlaybackSpeedMultiplier)
|
||||||
|
val currentPosition = converter.convert(controller!!.position)
|
||||||
|
val duration = converter.convert(controller!!.duration)
|
||||||
|
val remainingTime = converter.convert(controller!!.duration - controller!!.position)
|
||||||
|
// Log.d(TAG, "currentPosition " + Converter.getDurationStringLong(currentPosition));
|
||||||
|
if (currentPosition == Playable.INVALID_TIME || duration == Playable.INVALID_TIME) {
|
||||||
|
Log.w(TAG, "Could not react to position observer update because of invalid time")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
binding.positionLabel.text = getDurationStringLong(currentPosition)
|
||||||
|
if (showTimeLeft) {
|
||||||
|
binding.durationLabel.text = "-" + getDurationStringLong(remainingTime)
|
||||||
|
} else {
|
||||||
|
binding.durationLabel.text = getDurationStringLong(duration)
|
||||||
|
}
|
||||||
|
updateProgressbarPosition(currentPosition, duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateProgressbarPosition(position: Int, duration: Int) {
|
||||||
|
Log.d(TAG, "updateProgressbarPosition($position, $duration)")
|
||||||
|
val progress = (position.toFloat()) / duration
|
||||||
|
binding.sbPosition.progress = (progress * binding.sbPosition.max).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||||
|
if (controller == null) return
|
||||||
|
|
||||||
|
if (fromUser) {
|
||||||
|
prog = progress / (seekBar.max.toFloat())
|
||||||
|
val converter = TimeSpeedConverter(controller!!.currentPlaybackSpeedMultiplier)
|
||||||
|
val position = converter.convert((prog * controller!!.duration).toInt())
|
||||||
|
binding.seekPositionLabel.text = getDurationStringLong(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartTrackingTouch(seekBar: SeekBar) {
|
||||||
|
binding.seekCardView.scaleX = .8f
|
||||||
|
binding.seekCardView.scaleY = .8f
|
||||||
|
binding.seekCardView.animate()
|
||||||
|
.setInterpolator(FastOutSlowInInterpolator())
|
||||||
|
.alpha(1f).scaleX(1f).scaleY(1f)
|
||||||
|
.setDuration(200)
|
||||||
|
.start()
|
||||||
|
videoControlsHider.removeCallbacks(hideVideoControls)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStopTrackingTouch(seekBar: SeekBar) {
|
||||||
|
controller?.seekTo((prog * controller!!.duration).toInt())
|
||||||
|
|
||||||
|
binding.seekCardView.scaleX = 1f
|
||||||
|
binding.seekCardView.scaleY = 1f
|
||||||
|
binding.seekCardView.animate()
|
||||||
|
.setInterpolator(FastOutSlowInInterpolator())
|
||||||
|
.alpha(0f).scaleX(.8f).scaleY(.8f)
|
||||||
|
.setDuration(200)
|
||||||
|
.start()
|
||||||
|
setupVideoControlsToggler()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = "VideoplayerFragment"
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,13 +2,14 @@ package ac.mdiq.podcini.ui.view
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
|
import android.util.Log
|
||||||
import android.widget.VideoView
|
import android.widget.VideoView
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
|
|
||||||
class AspectRatioVideoView @JvmOverloads constructor(context: Context,
|
class AspectRatioVideoView @JvmOverloads constructor(context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyle: Int = 0
|
defStyle: Int = 0)
|
||||||
) : VideoView(context, attrs, defStyle) {
|
: VideoView(context, attrs, defStyle) {
|
||||||
|
|
||||||
private var mVideoWidth = 0
|
private var mVideoWidth = 0
|
||||||
private var mVideoHeight = 0
|
private var mVideoHeight = 0
|
||||||
|
@ -20,7 +21,7 @@ class AspectRatioVideoView @JvmOverloads constructor(context: Context,
|
||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Log.d(TAG, "onMeasure $mAvailableWidth $mAvailableHeight")
|
||||||
if (mAvailableWidth < 0 || mAvailableHeight < 0) {
|
if (mAvailableWidth < 0 || mAvailableHeight < 0) {
|
||||||
mAvailableWidth = width.toFloat()
|
mAvailableWidth = width.toFloat()
|
||||||
mAvailableHeight = height.toFloat()
|
mAvailableHeight = height.toFloat()
|
||||||
|
@ -54,6 +55,7 @@ class AspectRatioVideoView @JvmOverloads constructor(context: Context,
|
||||||
// Set the new video size
|
// Set the new video size
|
||||||
mVideoWidth = videoWidth
|
mVideoWidth = videoWidth
|
||||||
mVideoHeight = videoHeight
|
mVideoHeight = videoHeight
|
||||||
|
Log.d(TAG, "setVideoSize $mVideoWidth $mVideoHeight")
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If this isn't set the video is stretched across the
|
* If this isn't set the video is stretched across the
|
||||||
|
@ -64,8 +66,8 @@ class AspectRatioVideoView @JvmOverloads constructor(context: Context,
|
||||||
*/
|
*/
|
||||||
holder.setFixedSize(videoWidth, videoHeight)
|
holder.setFixedSize(videoWidth, videoHeight)
|
||||||
|
|
||||||
requestLayout()
|
// requestLayout()
|
||||||
invalidate()
|
// invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -76,6 +78,11 @@ class AspectRatioVideoView @JvmOverloads constructor(context: Context,
|
||||||
fun setAvailableSize(width: Float, height: Float) {
|
fun setAvailableSize(width: Float, height: Float) {
|
||||||
mAvailableWidth = width
|
mAvailableWidth = width
|
||||||
mAvailableHeight = height
|
mAvailableHeight = height
|
||||||
requestLayout()
|
Log.d(TAG, "setAvailableSize $mAvailableWidth $mAvailableHeight")
|
||||||
|
// requestLayout()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "AspectRatioVideoView"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ object WidgetUpdater {
|
||||||
val views = RemoteViews(context.packageName, R.layout.player_widget)
|
val views = RemoteViews(context.packageName, R.layout.player_widget)
|
||||||
|
|
||||||
if (widgetState.media != null) {
|
if (widgetState.media != null) {
|
||||||
val icon: Bitmap?
|
var icon: Bitmap? = null
|
||||||
val iconSize = context.resources.getDimensionPixelSize(android.R.dimen.app_icon_size)
|
val iconSize = context.resources.getDimensionPixelSize(android.R.dimen.app_icon_size)
|
||||||
views.setOnClickPendingIntent(R.id.layout_left, startMediaPlayer)
|
views.setOnClickPendingIntent(R.id.layout_left, startMediaPlayer)
|
||||||
views.setOnClickPendingIntent(R.id.imgvCover, startMediaPlayer)
|
views.setOnClickPendingIntent(R.id.imgvCover, startMediaPlayer)
|
||||||
|
@ -65,26 +65,21 @@ object WidgetUpdater {
|
||||||
.transform(FitCenter(), RoundedCorners(radius))
|
.transform(FitCenter(), RoundedCorners(radius))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var imgLoc = widgetState.media.getImageLocation()
|
val imgLoc = widgetState.media.getImageLocation()
|
||||||
if (imgLoc != null) {
|
val imgLoc1 = getFallbackImageLocation(widgetState.media)
|
||||||
icon = Glide.with(context)
|
icon = Glide.with(context)
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.load(imgLoc)
|
.load(imgLoc)
|
||||||
|
.error(Glide.with(context)
|
||||||
|
.asBitmap()
|
||||||
|
.load(imgLoc1)
|
||||||
|
.apply(options)
|
||||||
|
.submit(iconSize, iconSize)[500, TimeUnit.MILLISECONDS])
|
||||||
.apply(options)
|
.apply(options)
|
||||||
.submit(iconSize, iconSize)
|
.submit(iconSize, iconSize)
|
||||||
.get(500, TimeUnit.MILLISECONDS)
|
.get(500, TimeUnit.MILLISECONDS)
|
||||||
views.setImageViewBitmap(R.id.imgvCover, icon)
|
if (icon != null) views.setImageViewBitmap(R.id.imgvCover, icon)
|
||||||
} else {
|
else views.setImageViewResource(R.id.imgvCover, R.mipmap.ic_launcher)
|
||||||
imgLoc = getFallbackImageLocation(widgetState.media)
|
|
||||||
if (imgLoc != null) {
|
|
||||||
icon = Glide.with(context)
|
|
||||||
.asBitmap()
|
|
||||||
.load(imgLoc)
|
|
||||||
.apply(options)
|
|
||||||
.submit(iconSize, iconSize)[500, TimeUnit.MILLISECONDS]
|
|
||||||
views.setImageViewBitmap(R.id.imgvCover, icon)
|
|
||||||
} else views.setImageViewResource(R.id.imgvCover, R.mipmap.ic_launcher)
|
|
||||||
}
|
|
||||||
} catch (tr1: Throwable) {
|
} catch (tr1: Throwable) {
|
||||||
Log.e(TAG, "Error loading the media icon for the widget", tr1)
|
Log.e(TAG, "Error loading the media icon for the widget", tr1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector android:height="24dp"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:width="24dp"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FFFFFFFF"
|
||||||
|
android:strokeColor="#505050"
|
||||||
|
android:strokeWidth="0.75"
|
||||||
|
android:pathData="M7,14L5,14v5h5v-2L7,17v-3zM5,10h2L7,7h3L10,5L5,5v5zM17,17h-3v2h5v-5h-2v3zM14,5v2h3v3h2L19,5h-5z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,196 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:background="@color/black"
|
||||||
|
android:id="@+id/videoEpisodeContainer">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/black"
|
||||||
|
android:id="@+id/videoPlayerContainer">
|
||||||
|
|
||||||
|
<ac.mdiq.podcini.ui.view.AspectRatioVideoView
|
||||||
|
android:id="@+id/videoView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:indeterminateOnly="true"
|
||||||
|
android:visibility="invisible" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/controlsContainer"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layoutDirection="ltr"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/audioOnly"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="6dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/player_switch_to_audio_only"
|
||||||
|
app:srcCompat="@drawable/baseline_audiotrack_24" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/rewindButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="6dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/rewind_label"
|
||||||
|
app:srcCompat="@drawable/ic_fast_rewind_video_white" />
|
||||||
|
|
||||||
|
<ac.mdiq.podcini.ui.view.PlayButton
|
||||||
|
android:id="@+id/playButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="6dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/pause_label"
|
||||||
|
app:srcCompat="@drawable/ic_pause_video_white" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/fastForwardButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="6dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/fast_forward_label"
|
||||||
|
app:srcCompat="@drawable/ic_fast_forward_video_white" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/toggleViews"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="6dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/toggle_video_views"
|
||||||
|
app:srcCompat="@drawable/baseline_fullscreen_24" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/skipAnimationImage"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:padding="64dp"
|
||||||
|
android:layout_gravity="center"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/bottomControlsContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/seekCardView"
|
||||||
|
android:alpha="0"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
app:cardCornerRadius="8dp"
|
||||||
|
app:cardBackgroundColor="?attr/seek_background"
|
||||||
|
app:cardElevation="0dp"
|
||||||
|
tools:alpha="1">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/seekPositionLabel"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="24dp"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
android:paddingRight="24dp"
|
||||||
|
android:paddingBottom="4dp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="24sp"
|
||||||
|
tools:text="1:06:29" />
|
||||||
|
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:background="#80000000"
|
||||||
|
android:layoutDirection="ltr"
|
||||||
|
android:paddingTop="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/positionLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:text="@string/position_default_label"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/durationLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:text="@string/position_default_label"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<SeekBar
|
||||||
|
android:id="@+id/sbPosition"
|
||||||
|
android:layout_width="0px"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_toLeftOf="@+id/durationLabel"
|
||||||
|
android:layout_toStartOf="@+id/durationLabel"
|
||||||
|
android:layout_toRightOf="@+id/positionLabel"
|
||||||
|
android:layout_toEndOf="@+id/positionLabel"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:max="500" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<ac.mdiq.podcini.ui.view.ShownotesWebView
|
||||||
|
android:id="@+id/webvDescription"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/videoPlayerContainer"
|
||||||
|
android:foreground="?android:windowContentOverlay" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
|
@ -5,158 +5,15 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:background="@color/black"
|
android:background="@color/black"
|
||||||
android:orientation="vertical"
|
android:id="@+id/videoPlayerContainer"
|
||||||
android:id="@+id/videoPlayerContainer">
|
android:windowActionBarOverlay="true">
|
||||||
|
|
||||||
<ac.mdiq.podcini.ui.view.AspectRatioVideoView
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/videoView"
|
android:id="@+id/main_view"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center" />
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/progressBar"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:indeterminateOnly="true"
|
|
||||||
android:visibility="invisible" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/controlsContainer"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layoutDirection="ltr"
|
|
||||||
android:background="@android:color/transparent"
|
|
||||||
android:padding="16dp"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/rewindButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="8dp"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:contentDescription="@string/rewind_label"
|
|
||||||
app:srcCompat="@drawable/ic_fast_rewind_video_white" />
|
|
||||||
|
|
||||||
<ac.mdiq.podcini.ui.view.PlayButton
|
|
||||||
android:id="@+id/playButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="8dp"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:contentDescription="@string/pause_label"
|
|
||||||
app:srcCompat="@drawable/ic_pause_video_white" />
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/fastForwardButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="8dp"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:contentDescription="@string/fast_forward_label"
|
|
||||||
app:srcCompat="@drawable/ic_fast_forward_video_white" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/skipAnimationImage"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:padding="64dp"
|
|
||||||
android:layout_gravity="center"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/bottomControlsContainer"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="bottom|center"
|
android:foreground="?android:windowContentOverlay"
|
||||||
android:orientation="vertical">
|
tools:background="@android:color/holo_red_dark"
|
||||||
|
android:windowActionBarOverlay="true"/>
|
||||||
<androidx.cardview.widget.CardView
|
|
||||||
android:id="@+id/seekCardView"
|
|
||||||
android:alpha="0"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="12dp"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
app:cardCornerRadius="8dp"
|
|
||||||
app:cardBackgroundColor="?attr/seek_background"
|
|
||||||
app:cardElevation="0dp"
|
|
||||||
tools:alpha="1">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/seekPositionLabel"
|
|
||||||
android:gravity="center"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingLeft="24dp"
|
|
||||||
android:paddingTop="4dp"
|
|
||||||
android:paddingRight="24dp"
|
|
||||||
android:paddingBottom="4dp"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="24sp"
|
|
||||||
tools:text="1:06:29" />
|
|
||||||
|
|
||||||
</androidx.cardview.widget.CardView>
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="50dp"
|
|
||||||
android:background="#80000000"
|
|
||||||
android:layoutDirection="ltr"
|
|
||||||
android:paddingTop="8dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/positionLabel"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:layout_marginLeft="8dp"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginRight="8dp"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:text="@string/position_default_label"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/durationLabel"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentRight="true"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:layout_marginLeft="8dp"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginRight="8dp"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:text="@string/position_default_label"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<SeekBar
|
|
||||||
android:id="@+id/sbPosition"
|
|
||||||
android:layout_width="0px"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_toLeftOf="@+id/durationLabel"
|
|
||||||
android:layout_toStartOf="@+id/durationLabel"
|
|
||||||
android:layout_toRightOf="@+id/positionLabel"
|
|
||||||
android:layout_toEndOf="@+id/positionLabel"
|
|
||||||
android:layout_centerInParent="true"
|
|
||||||
android:max="500" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 10 KiB |
|
@ -177,6 +177,17 @@
|
||||||
<item>@string/add_feed_label</item>
|
<item>@string/add_feed_label</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="video_mode_options">
|
||||||
|
<item>@string/pref_video_mode_small_window</item>
|
||||||
|
<item>@string/pref_video_mode_full_screen</item>
|
||||||
|
<item>@string/pref_video_mode_audio_only</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="video_mode_options_values">
|
||||||
|
<item>1</item>
|
||||||
|
<item>2</item>
|
||||||
|
<item>3</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
<string-array name="nav_drawer_feed_order_options">
|
<string-array name="nav_drawer_feed_order_options">
|
||||||
<item>@string/drawer_feed_order_unplayed_episodes</item>
|
<item>@string/drawer_feed_order_unplayed_episodes</item>
|
||||||
<item>@string/drawer_feed_order_alphabetical</item>
|
<item>@string/drawer_feed_order_alphabetical</item>
|
||||||
|
|
|
@ -487,6 +487,11 @@
|
||||||
<string name="pref_fast_forward_sum">Customize the number of seconds to jump forward when the fast forward button is clicked</string>
|
<string name="pref_fast_forward_sum">Customize the number of seconds to jump forward when the fast forward button is clicked</string>
|
||||||
<string name="pref_rewind">Rewind skip time</string>
|
<string name="pref_rewind">Rewind skip time</string>
|
||||||
<string name="pref_rewind_sum">Customize the number of seconds to jump backwards when the rewind button is clicked</string>
|
<string name="pref_rewind_sum">Customize the number of seconds to jump backwards when the rewind button is clicked</string>
|
||||||
|
<string name="pref_playback_video_mode">Video play mode</string>
|
||||||
|
<string name="pref_playback_video_mode_sum">Choose how video episode is played by default</string>
|
||||||
|
<string name="pref_video_mode_full_screen">Full screen</string>
|
||||||
|
<string name="pref_video_mode_small_window">Small window</string>
|
||||||
|
<string name="pref_video_mode_audio_only">Audio only</string>
|
||||||
<string name="pref_expandNotify_title">High notification priority</string>
|
<string name="pref_expandNotify_title">High notification priority</string>
|
||||||
<string name="pref_expandNotify_sum">This usually expands the notification to show playback buttons.</string>
|
<string name="pref_expandNotify_sum">This usually expands the notification to show playback buttons.</string>
|
||||||
<string name="pref_persistNotify_title">Persistent playback controls</string>
|
<string name="pref_persistNotify_title">Persistent playback controls</string>
|
||||||
|
@ -688,6 +693,7 @@
|
||||||
<string name="toolbar_back_button_content_description">Back</string>
|
<string name="toolbar_back_button_content_description">Back</string>
|
||||||
<string name="rewind_label">Rewind</string>
|
<string name="rewind_label">Rewind</string>
|
||||||
<string name="fast_forward_label">Fast forward</string>
|
<string name="fast_forward_label">Fast forward</string>
|
||||||
|
<string name="toggle_video_views">Toggle video views</string>
|
||||||
<string name="increase_speed">Increase speed</string>
|
<string name="increase_speed">Increase speed</string>
|
||||||
<string name="decrease_speed">Decrease speed</string>
|
<string name="decrease_speed">Decrease speed</string>
|
||||||
<string name="media_type_video_label">Video</string>
|
<string name="media_type_video_label">Video</string>
|
||||||
|
|
|
@ -227,6 +227,16 @@
|
||||||
<item name="windowSplashScreenAnimatedIcon">@drawable/launcher_animate</item>
|
<item name="windowSplashScreenAnimatedIcon">@drawable/launcher_animate</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="Theme.Podcini.VideoEpisode" parent="@style/Theme.Podcini.Dark">
|
||||||
|
<item name="android:fitsSystemWindows">true</item>
|
||||||
|
<!-- <item name="android:windowActionModeOverlay">true</item>-->
|
||||||
|
<!-- <item name="android:windowActionBarOverlay">true</item>-->
|
||||||
|
<!-- <item name="android:windowActionBar">true</item>-->
|
||||||
|
<!-- <item name="windowActionModeOverlay">true</item>-->
|
||||||
|
<!-- <item name="windowActionBar">true</item>-->
|
||||||
|
<item name="windowActionBarOverlay">true</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Podcini.VideoPlayer" parent="@style/Theme.Podcini.Dark">
|
<style name="Theme.Podcini.VideoPlayer" parent="@style/Theme.Podcini.Dark">
|
||||||
<item name="windowActionBarOverlay">true</item>
|
<item name="windowActionBarOverlay">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -61,6 +61,11 @@
|
||||||
android:key="prefStreamOverDownload"
|
android:key="prefStreamOverDownload"
|
||||||
android:summary="@string/pref_stream_over_download_sum"
|
android:summary="@string/pref_stream_over_download_sum"
|
||||||
android:title="@string/pref_stream_over_download_title"/>
|
android:title="@string/pref_stream_over_download_title"/>
|
||||||
|
<Preference
|
||||||
|
android:title="@string/pref_playback_video_mode"
|
||||||
|
android:key="prefPlaybackVideoModeLauncher"
|
||||||
|
android:summary="@string/pref_playback_video_mode_sum"/>
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/reassign_hardware_buttons">
|
<PreferenceCategory android:title="@string/reassign_hardware_buttons">
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
android:title="@string/pref_nav_drawer_feed_counter_title"
|
android:title="@string/pref_nav_drawer_feed_counter_title"
|
||||||
android:key="prefDrawerFeedIndicator"
|
android:key="prefDrawerFeedIndicator"
|
||||||
android:summary="@string/pref_nav_drawer_feed_counter_sum"
|
android:summary="@string/pref_nav_drawer_feed_counter_sum"
|
||||||
android:defaultValue="1"/>
|
android:defaultValue="2"/>
|
||||||
<!-- <Preference-->
|
<!-- <Preference-->
|
||||||
<!-- android:title="@string/pref_filter_feed_title"-->
|
<!-- android:title="@string/pref_filter_feed_title"-->
|
||||||
<!-- android:key="prefSubscriptionsFilter"-->
|
<!-- android:key="prefSubscriptionsFilter"-->
|
||||||
|
|
12
changelog.md
12
changelog.md
|
@ -261,3 +261,15 @@
|
||||||
* fixed bug of receiving null view in function requiring non-null in subscriptions page
|
* fixed bug of receiving null view in function requiring non-null in subscriptions page
|
||||||
* set Counter default to number of SHOW_UNPLAYED
|
* set Counter default to number of SHOW_UNPLAYED
|
||||||
* removed FeedCounter.SHOW_NEW, NewEpisodesNotification, and associated notifications settings
|
* removed FeedCounter.SHOW_NEW, NewEpisodesNotification, and associated notifications settings
|
||||||
|
|
||||||
|
## 4.8.0
|
||||||
|
|
||||||
|
* fixed empty player detailed view on first start
|
||||||
|
* player detailed view scrolls to the top on a new episode
|
||||||
|
* created video episode view, with video player on top and episode descriptions in portrait mode
|
||||||
|
* added video player mode setting in preferences, set to small window by default
|
||||||
|
* added on video controller easy switches to other video mode or audio only
|
||||||
|
* when video mode is set to audio only, click on image on audio player on a video episode brings up the normal player detailed view
|
||||||
|
* webkit updated to Androidx
|
||||||
|
* fixed bug in setting speed to wrong categories
|
||||||
|
* improved fetching of episode images when invalid addresses are given
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
Version 4.8.0 brings several changes:
|
||||||
|
|
||||||
|
* fixed empty player detailed view on first start
|
||||||
|
* player detailed view scrolls to the top on a new episode
|
||||||
|
* created video episode view, with video player on top and episode descriptions in portrait mode
|
||||||
|
* added video player mode setting in preferences, set to small window by default
|
||||||
|
* added on video controller easy switches to other video mode or audio only
|
||||||
|
* when video mode is set to audio only, click on image on audio player on a video episode brings up the normal player detailed view
|
||||||
|
* webkit updated to Androidx
|
||||||
|
* fixed bug in setting speed to wrong categories
|
||||||
|
* improved fetching of episode images when invalid addresses are given
|
Loading…
Reference in New Issue