tuning and bug fixes
This commit is contained in:
parent
9a96130fa7
commit
0e36e6f39e
|
@ -15,6 +15,7 @@ target/
|
||||||
build/
|
build/
|
||||||
**/*.project
|
**/*.project
|
||||||
**/*.classpath
|
**/*.classpath
|
||||||
|
**/.directory
|
||||||
|
|
||||||
# Local configuration file (sdk path, etc)
|
# Local configuration file (sdk path, etc)
|
||||||
local.properties
|
local.properties
|
||||||
|
|
|
@ -22,8 +22,8 @@ android {
|
||||||
// Version code schema:
|
// Version code schema:
|
||||||
// "1.2.3-beta4" -> 1020304
|
// "1.2.3-beta4" -> 1020304
|
||||||
// "1.2.3" -> 1020395
|
// "1.2.3" -> 1020395
|
||||||
versionCode 3020095
|
versionCode 3020096
|
||||||
versionName "3.2.0"
|
versionName "3.2.1"
|
||||||
|
|
||||||
def commit = ""
|
def commit = ""
|
||||||
try {
|
try {
|
||||||
|
@ -65,6 +65,12 @@ android {
|
||||||
signingConfig signingConfigs.releaseConfig
|
signingConfig signingConfigs.releaseConfig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
applicationVariants.all { variant ->
|
||||||
|
variant.outputs.all { output ->
|
||||||
|
def applicationName = "PodVinci"
|
||||||
|
outputFileName = "${applicationName}_${variant.buildType.name}_${defaultConfig.versionName}.apk"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
androidResources {
|
androidResources {
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.lang.ref.WeakReference
|
||||||
*/
|
*/
|
||||||
open class EpisodeItemListAdapter(mainActivity: MainActivity) : SelectableAdapter<EpisodeItemViewHolder?>(mainActivity),
|
open class EpisodeItemListAdapter(mainActivity: MainActivity) : SelectableAdapter<EpisodeItemViewHolder?>(mainActivity),
|
||||||
View.OnCreateContextMenuListener {
|
View.OnCreateContextMenuListener {
|
||||||
|
|
||||||
private val mainActivityRef: WeakReference<MainActivity> = WeakReference<MainActivity>(mainActivity)
|
private val mainActivityRef: WeakReference<MainActivity> = WeakReference<MainActivity>(mainActivity)
|
||||||
private var episodes: List<FeedItem> = ArrayList()
|
private var episodes: List<FeedItem> = ArrayList()
|
||||||
var longPressedItem: FeedItem? = null
|
var longPressedItem: FeedItem? = null
|
||||||
|
@ -178,18 +179,22 @@ open class EpisodeItemListAdapter(mainActivity: MainActivity) : SelectableAdapte
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onContextItemSelected(item: MenuItem): Boolean {
|
fun onContextItemSelected(item: MenuItem): Boolean {
|
||||||
if (item.itemId == R.id.multi_select) {
|
when (item.itemId) {
|
||||||
startSelectMode(longPressedPosition)
|
R.id.multi_select -> {
|
||||||
return true
|
startSelectMode(longPressedPosition)
|
||||||
} else if (item.itemId == R.id.select_all_above) {
|
return true
|
||||||
setSelected(0, longPressedPosition, true)
|
}
|
||||||
return true
|
R.id.select_all_above -> {
|
||||||
} else if (item.itemId == R.id.select_all_below) {
|
setSelected(0, longPressedPosition, true)
|
||||||
shouldSelectLazyLoadedItems = true
|
return true
|
||||||
setSelected(longPressedPosition + 1, itemCount, true)
|
}
|
||||||
return true
|
R.id.select_all_below -> {
|
||||||
|
shouldSelectLazyLoadedItems = true
|
||||||
|
setSelected(longPressedPosition + 1, itemCount, true)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
else -> return false
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val selectedItems: List<Any>
|
val selectedItems: List<Any>
|
||||||
|
|
|
@ -12,6 +12,7 @@ import ac.mdiq.podvinci.R
|
||||||
*/
|
*/
|
||||||
abstract class SelectableAdapter<T : RecyclerView.ViewHolder?>(private val activity: Activity) :
|
abstract class SelectableAdapter<T : RecyclerView.ViewHolder?>(private val activity: Activity) :
|
||||||
RecyclerView.Adapter<T>() {
|
RecyclerView.Adapter<T>() {
|
||||||
|
|
||||||
private var actionMode: ActionMode? = null
|
private var actionMode: ActionMode? = null
|
||||||
private val selectedIds = HashSet<Long>()
|
private val selectedIds = HashSet<Long>()
|
||||||
private var onSelectModeListener: OnSelectModeListener? = null
|
private var onSelectModeListener: OnSelectModeListener? = null
|
||||||
|
|
|
@ -523,7 +523,8 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
|
||||||
.subscribe(
|
.subscribe(
|
||||||
{ result: Feed? ->
|
{ result: Feed? ->
|
||||||
feed = result
|
feed = result
|
||||||
if (feed != null) swipeActions?.setFilter(feed!!.itemFilter)
|
Log.d(TAG, "loadItems subscribe called ${feed?.title}")
|
||||||
|
swipeActions?.setFilter(feed?.itemFilter)
|
||||||
refreshHeaderView()
|
refreshHeaderView()
|
||||||
viewBinding!!.progressBar.visibility = View.GONE
|
viewBinding!!.progressBar.visibility = View.GONE
|
||||||
adapter?.setDummyViews(0)
|
adapter?.setDummyViews(0)
|
||||||
|
@ -541,11 +542,12 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
|
||||||
|
|
||||||
private fun loadData(): Feed? {
|
private fun loadData(): Feed? {
|
||||||
val feed: Feed = DBReader.getFeed(feedID, true) ?: return null
|
val feed: Feed = DBReader.getFeed(feedID, true) ?: return null
|
||||||
|
Log.d(TAG, "loadData got feed ${feed.title} with items: ${feed.items.size} ${feed.items[0].getPubDate()}")
|
||||||
if (feed.items.isNotEmpty()) {
|
if (feed.items.isNotEmpty()) {
|
||||||
DBReader.loadAdditionalFeedItemListData(feed.items)
|
DBReader.loadAdditionalFeedItemListData(feed.items)
|
||||||
if (feed.sortOrder != null) {
|
if (feed.sortOrder != null) {
|
||||||
val feedItems: MutableList<FeedItem> = feed.items
|
val feedItems: MutableList<FeedItem> = feed.items
|
||||||
FeedItemPermutors.getPermutor(feed.sortOrder!!).reorder(feedItems.toMutableList())
|
FeedItemPermutors.getPermutor(feed.sortOrder!!).reorder(feedItems)
|
||||||
feed.items = feedItems
|
feed.items = feedItems
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.activity.result.ActivityResult
|
import androidx.activity.result.ActivityResult
|
||||||
import androidx.activity.result.ActivityResultCallback
|
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
|
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
|
||||||
|
@ -33,12 +32,10 @@ import ac.mdiq.podvinci.core.export.ExportWriter
|
||||||
import ac.mdiq.podvinci.core.export.favorites.FavoritesWriter
|
import ac.mdiq.podvinci.core.export.favorites.FavoritesWriter
|
||||||
import ac.mdiq.podvinci.core.export.html.HtmlWriter
|
import ac.mdiq.podvinci.core.export.html.HtmlWriter
|
||||||
import ac.mdiq.podvinci.core.export.opml.OpmlWriter
|
import ac.mdiq.podvinci.core.export.opml.OpmlWriter
|
||||||
import ac.mdiq.podvinci.core.storage.DatabaseExporter
|
import ac.mdiq.podvinci.core.storage.DatabaseTransporter
|
||||||
import io.reactivex.Completable
|
import io.reactivex.Completable
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
import io.reactivex.functions.Action
|
|
||||||
import io.reactivex.functions.Consumer
|
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
@ -237,7 +234,7 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
||||||
}
|
}
|
||||||
val uri = result.data!!.data
|
val uri = result.data!!.data
|
||||||
progressDialog!!.show()
|
progressDialog!!.show()
|
||||||
disposable = Completable.fromAction { DatabaseExporter.importBackup(uri, requireContext()) }
|
disposable = Completable.fromAction { DatabaseTransporter.importBackup(uri, requireContext()) }
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe({
|
.subscribe({
|
||||||
|
@ -251,7 +248,7 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
progressDialog!!.show()
|
progressDialog!!.show()
|
||||||
disposable = Completable.fromAction { DatabaseExporter.exportToDocument(uri, requireContext()) }
|
disposable = Completable.fromAction { DatabaseTransporter.exportToDocument(uri, requireContext()) }
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe({
|
.subscribe({
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
package ac.mdiq.podvinci.core.service.playback
|
package ac.mdiq.podvinci.core.service.playback
|
||||||
|
|
||||||
|
import ac.mdiq.podvinci.core.ClientConfig
|
||||||
|
import ac.mdiq.podvinci.core.R
|
||||||
|
import ac.mdiq.podvinci.core.service.download.HttpCredentialEncoder
|
||||||
|
import ac.mdiq.podvinci.core.service.download.PodVinciHttpClient
|
||||||
|
import ac.mdiq.podvinci.core.util.NetworkUtils.wasDownloadBlocked
|
||||||
|
import ac.mdiq.podvinci.model.playback.Playable
|
||||||
|
import ac.mdiq.podvinci.storage.preferences.UserPreferences
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.media.audiofx.LoudnessEnhancer
|
import android.media.audiofx.LoudnessEnhancer
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
@ -13,11 +20,9 @@ import androidx.media3.common.TrackSelectionParameters.AudioOffloadPreferences
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.datasource.DataSource
|
import androidx.media3.datasource.DataSource
|
||||||
import androidx.media3.datasource.DefaultDataSourceFactory
|
import androidx.media3.datasource.DefaultDataSourceFactory
|
||||||
|
import androidx.media3.datasource.HttpDataSource.HttpDataSourceException
|
||||||
import androidx.media3.datasource.okhttp.OkHttpDataSource
|
import androidx.media3.datasource.okhttp.OkHttpDataSource
|
||||||
import androidx.media3.exoplayer.DefaultLoadControl
|
import androidx.media3.exoplayer.*
|
||||||
import androidx.media3.exoplayer.DefaultRenderersFactory
|
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
|
||||||
import androidx.media3.exoplayer.SeekParameters
|
|
||||||
import androidx.media3.exoplayer.source.MediaSource
|
import androidx.media3.exoplayer.source.MediaSource
|
||||||
import androidx.media3.exoplayer.source.ProgressiveMediaSource
|
import androidx.media3.exoplayer.source.ProgressiveMediaSource
|
||||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
||||||
|
@ -27,39 +32,37 @@ import androidx.media3.extractor.DefaultExtractorsFactory
|
||||||
import androidx.media3.extractor.mp3.Mp3Extractor
|
import androidx.media3.extractor.mp3.Mp3Extractor
|
||||||
import androidx.media3.ui.DefaultTrackNameProvider
|
import androidx.media3.ui.DefaultTrackNameProvider
|
||||||
import androidx.media3.ui.TrackNameProvider
|
import androidx.media3.ui.TrackNameProvider
|
||||||
import ac.mdiq.podvinci.core.ClientConfig
|
|
||||||
import ac.mdiq.podvinci.core.service.download.PodVinciHttpClient
|
|
||||||
import ac.mdiq.podvinci.core.service.download.HttpCredentialEncoder
|
|
||||||
import ac.mdiq.podvinci.model.playback.Playable
|
|
||||||
import ac.mdiq.podvinci.storage.preferences.UserPreferences
|
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
class ExoPlayerWrapper internal constructor(private val context: Context) {
|
class ExoPlayerWrapper internal constructor(private val context: Context) {
|
||||||
|
// TODO: need to experiment this, 5 seconds for now
|
||||||
|
private val bufferUpdateInterval = 5L
|
||||||
|
|
||||||
private val bufferingUpdateDisposable: Disposable
|
private val bufferingUpdateDisposable: Disposable
|
||||||
private var exoPlayer: ExoPlayer? = null
|
private lateinit var exoPlayer: ExoPlayer
|
||||||
|
private lateinit var trackSelector: DefaultTrackSelector
|
||||||
|
private var loudnessEnhancer: LoudnessEnhancer? = null
|
||||||
|
|
||||||
private var mediaSource: MediaSource? = null
|
private var mediaSource: MediaSource? = null
|
||||||
private var audioSeekCompleteListener: Runnable? = null
|
private var audioSeekCompleteListener: Runnable? = null
|
||||||
private var audioCompletionListener: Runnable? = null
|
private var audioCompletionListener: Runnable? = null
|
||||||
private var audioErrorListener: Consumer<String>? = null
|
private var audioErrorListener: Consumer<String>? = null
|
||||||
private var bufferingUpdateListener: Consumer<Int>? = null
|
private var bufferingUpdateListener: Consumer<Int>? = null
|
||||||
private var playbackParameters: PlaybackParameters
|
|
||||||
private var trackSelector: DefaultTrackSelector? = null
|
|
||||||
|
|
||||||
private var loudnessEnhancer: LoudnessEnhancer? = null
|
private var playbackParameters: PlaybackParameters
|
||||||
|
|
||||||
init {
|
init {
|
||||||
createPlayer()
|
createPlayer()
|
||||||
playbackParameters = exoPlayer!!.playbackParameters
|
playbackParameters = exoPlayer.playbackParameters
|
||||||
bufferingUpdateDisposable = Observable.interval(2, TimeUnit.SECONDS)
|
bufferingUpdateDisposable = Observable.interval(bufferUpdateInterval, TimeUnit.SECONDS)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe { tickNumber: Long? ->
|
.subscribe { tickNumber: Long? ->
|
||||||
if (bufferingUpdateListener != null) {
|
bufferingUpdateListener?.accept(exoPlayer.bufferedPercentage)
|
||||||
bufferingUpdateListener!!.accept(exoPlayer!!.bufferedPercentage)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,56 +73,53 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
|
||||||
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS)
|
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS)
|
||||||
loadControl.setBackBuffer(UserPreferences.rewindSecs * 1000 + 500, true)
|
loadControl.setBackBuffer(UserPreferences.rewindSecs * 1000 + 500, true)
|
||||||
trackSelector = DefaultTrackSelector(context)
|
trackSelector = DefaultTrackSelector(context)
|
||||||
val audioOffloadPreferences =
|
val audioOffloadPreferences = AudioOffloadPreferences.Builder()
|
||||||
AudioOffloadPreferences.Builder()
|
.setAudioOffloadMode(AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_ENABLED) // Add additional options as needed
|
||||||
.setAudioOffloadMode(AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_ENABLED) // Add additional options as needed
|
.setIsGaplessSupportRequired(true)
|
||||||
.setIsGaplessSupportRequired(true)
|
.build()
|
||||||
.build()
|
|
||||||
exoPlayer = ExoPlayer.Builder(context, DefaultRenderersFactory(context))
|
exoPlayer = ExoPlayer.Builder(context, DefaultRenderersFactory(context))
|
||||||
.setTrackSelector(trackSelector!!)
|
.setTrackSelector(trackSelector)
|
||||||
.setLoadControl(loadControl.build())
|
.setLoadControl(loadControl.build())
|
||||||
.build()
|
.build()
|
||||||
exoPlayer!!.setSeekParameters(SeekParameters.EXACT)
|
exoPlayer.setSeekParameters(SeekParameters.EXACT)
|
||||||
exoPlayer!!.trackSelectionParameters = exoPlayer!!.trackSelectionParameters
|
exoPlayer.trackSelectionParameters = exoPlayer.trackSelectionParameters
|
||||||
.buildUpon()
|
.buildUpon()
|
||||||
.setAudioOffloadPreferences(audioOffloadPreferences)
|
.setAudioOffloadPreferences(audioOffloadPreferences)
|
||||||
.build()
|
.build()
|
||||||
exoPlayer!!.addListener(object : Player.Listener {
|
exoPlayer.addListener(object : Player.Listener {
|
||||||
override fun onPlaybackStateChanged(playbackState: @Player.State Int) {
|
override fun onPlaybackStateChanged(playbackState: @Player.State Int) {
|
||||||
if (audioCompletionListener != null && playbackState == Player.STATE_ENDED) {
|
if (audioCompletionListener != null && playbackState == Player.STATE_ENDED) {
|
||||||
audioCompletionListener!!.run()
|
audioCompletionListener?.run()
|
||||||
} else if (bufferingUpdateListener != null && playbackState == Player.STATE_BUFFERING) {
|
} else if (playbackState == Player.STATE_BUFFERING) {
|
||||||
bufferingUpdateListener!!.accept(BUFFERING_STARTED)
|
bufferingUpdateListener?.accept(BUFFERING_STARTED)
|
||||||
} else if (bufferingUpdateListener != null) {
|
} else {
|
||||||
bufferingUpdateListener!!.accept(BUFFERING_ENDED)
|
bufferingUpdateListener?.accept(BUFFERING_ENDED)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPlayerError(error: PlaybackException) {
|
||||||
|
if (wasDownloadBlocked(error)) {
|
||||||
|
audioErrorListener?.accept(context.getString(R.string.download_error_blocked))
|
||||||
|
} else {
|
||||||
|
var cause = error.cause
|
||||||
|
if (cause is HttpDataSourceException) {
|
||||||
|
if (cause.cause != null) {
|
||||||
|
cause = cause.cause
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cause != null && "Source error" == cause.message) {
|
||||||
|
cause = cause.cause
|
||||||
|
}
|
||||||
|
audioErrorListener?.accept(if (cause != null) cause.message else error.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public void onPlayerError(@NonNull ExoPlaybackException error) {
|
|
||||||
// if (audioErrorListener != null) {
|
|
||||||
// if (NetworkUtils.wasDownloadBlocked(error)) {
|
|
||||||
// audioErrorListener.accept(context.getString(R.string.download_error_blocked));
|
|
||||||
// } else {
|
|
||||||
// Throwable cause = error.getCause();
|
|
||||||
// if (cause instanceof HttpDataSource.HttpDataSourceException) {
|
|
||||||
// if (cause.getCause() != null) {
|
|
||||||
// cause = cause.getCause();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if (cause != null && "Source error".equals(cause.getMessage())) {
|
|
||||||
// cause = cause.getCause();
|
|
||||||
// }
|
|
||||||
// audioErrorListener.accept(cause != null ? cause.getMessage() : error.getMessage());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
override fun onPositionDiscontinuity(oldPosition: PositionInfo,
|
override fun onPositionDiscontinuity(oldPosition: PositionInfo,
|
||||||
newPosition: PositionInfo,
|
newPosition: PositionInfo,
|
||||||
reason: @DiscontinuityReason Int
|
reason: @DiscontinuityReason Int
|
||||||
) {
|
) {
|
||||||
if (audioSeekCompleteListener != null && reason == Player.DISCONTINUITY_REASON_SEEK) {
|
if (reason == Player.DISCONTINUITY_REASON_SEEK) {
|
||||||
audioSeekCompleteListener!!.run()
|
audioSeekCompleteListener?.run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,41 +128,40 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
initLoudnessEnhancer(exoPlayer!!.audioSessionId)
|
initLoudnessEnhancer(exoPlayer.audioSessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
val currentPosition: Int
|
val currentPosition: Int
|
||||||
get() = exoPlayer!!.currentPosition.toInt()
|
get() = exoPlayer.currentPosition.toInt()
|
||||||
|
|
||||||
val currentSpeedMultiplier: Float
|
val currentSpeedMultiplier: Float
|
||||||
get() = playbackParameters.speed
|
get() = playbackParameters.speed
|
||||||
|
|
||||||
val duration: Int
|
val duration: Int
|
||||||
get() {
|
get() {
|
||||||
if (exoPlayer!!.duration == C.TIME_UNSET) {
|
if (exoPlayer.duration == C.TIME_UNSET) {
|
||||||
return Playable.INVALID_TIME
|
return Playable.INVALID_TIME
|
||||||
}
|
}
|
||||||
return exoPlayer!!.duration.toInt()
|
return exoPlayer.duration.toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
val isPlaying: Boolean
|
val isPlaying: Boolean
|
||||||
get() = exoPlayer!!.playWhenReady
|
get() = exoPlayer.playWhenReady
|
||||||
|
|
||||||
fun pause() {
|
fun pause() {
|
||||||
exoPlayer!!.pause()
|
exoPlayer.pause()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IllegalStateException::class)
|
@Throws(IllegalStateException::class)
|
||||||
fun prepare() {
|
fun prepare() {
|
||||||
exoPlayer!!.setMediaSource(mediaSource!!, false)
|
exoPlayer.setMediaSource(mediaSource!!, false)
|
||||||
exoPlayer!!.prepare()
|
exoPlayer.prepare()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun release() {
|
fun release() {
|
||||||
bufferingUpdateDisposable.dispose()
|
bufferingUpdateDisposable.dispose()
|
||||||
if (exoPlayer != null) {
|
exoPlayer.release()
|
||||||
exoPlayer!!.release()
|
|
||||||
}
|
|
||||||
audioSeekCompleteListener = null
|
audioSeekCompleteListener = null
|
||||||
audioCompletionListener = null
|
audioCompletionListener = null
|
||||||
audioErrorListener = null
|
audioErrorListener = null
|
||||||
|
@ -170,25 +169,23 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reset() {
|
fun reset() {
|
||||||
exoPlayer!!.release()
|
exoPlayer.release()
|
||||||
createPlayer()
|
createPlayer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IllegalStateException::class)
|
@Throws(IllegalStateException::class)
|
||||||
fun seekTo(i: Int) {
|
fun seekTo(i: Int) {
|
||||||
exoPlayer!!.seekTo(i.toLong())
|
exoPlayer.seekTo(i.toLong())
|
||||||
if (audioSeekCompleteListener != null) {
|
audioSeekCompleteListener?.run()
|
||||||
audioSeekCompleteListener!!.run()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setAudioStreamType(i: Int) {
|
fun setAudioStreamType(i: Int) {
|
||||||
val a = exoPlayer!!.audioAttributes
|
val a = exoPlayer.audioAttributes
|
||||||
val b = AudioAttributes.Builder()
|
val b = AudioAttributes.Builder()
|
||||||
b.setContentType(i)
|
b.setContentType(i)
|
||||||
b.setFlags(a.flags)
|
b.setFlags(a.flags)
|
||||||
b.setUsage(a.usage)
|
b.setUsage(a.usage)
|
||||||
exoPlayer!!.setAudioAttributes(b.build(), false)
|
exoPlayer.setAudioAttributes(b.build(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IllegalArgumentException::class, IllegalStateException::class)
|
@Throws(IllegalArgumentException::class, IllegalStateException::class)
|
||||||
|
@ -222,34 +219,34 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setDisplay(sh: SurfaceHolder?) {
|
fun setDisplay(sh: SurfaceHolder?) {
|
||||||
exoPlayer!!.setVideoSurfaceHolder(sh)
|
exoPlayer.setVideoSurfaceHolder(sh)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setPlaybackParams(speed: Float, skipSilence: Boolean) {
|
fun setPlaybackParams(speed: Float, skipSilence: Boolean) {
|
||||||
playbackParameters = PlaybackParameters(speed, playbackParameters.pitch)
|
playbackParameters = PlaybackParameters(speed, playbackParameters.pitch)
|
||||||
exoPlayer!!.skipSilenceEnabled = skipSilence
|
exoPlayer.skipSilenceEnabled = skipSilence
|
||||||
exoPlayer!!.playbackParameters = playbackParameters
|
exoPlayer.playbackParameters = playbackParameters
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setVolume(v: Float, v1: Float) {
|
fun setVolume(v: Float, v1: Float) {
|
||||||
if (v > 1) {
|
if (v > 1) {
|
||||||
exoPlayer!!.volume = 1f
|
exoPlayer.volume = 1f
|
||||||
loudnessEnhancer!!.setEnabled(true)
|
loudnessEnhancer?.setEnabled(true)
|
||||||
loudnessEnhancer!!.setTargetGain((1000 * (v - 1)).toInt())
|
loudnessEnhancer?.setTargetGain((1000 * (v - 1)).toInt())
|
||||||
} else {
|
} else {
|
||||||
exoPlayer!!.volume = v
|
exoPlayer.volume = v
|
||||||
loudnessEnhancer!!.setEnabled(false)
|
loudnessEnhancer?.setEnabled(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
exoPlayer!!.play()
|
exoPlayer.play()
|
||||||
// Can't set params when paused - so always set it on start in case they changed
|
// Can't set params when paused - so always set it on start in case they changed
|
||||||
exoPlayer!!.playbackParameters = playbackParameters
|
exoPlayer.playbackParameters = playbackParameters
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
exoPlayer!!.stop()
|
exoPlayer.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
val audioTracks: List<String>
|
val audioTracks: List<String>
|
||||||
|
@ -264,8 +261,8 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
|
||||||
|
|
||||||
private val formats: List<Format>
|
private val formats: List<Format>
|
||||||
get() {
|
get() {
|
||||||
val formats: MutableList<Format> = ArrayList()
|
val formats: MutableList<Format> = arrayListOf()
|
||||||
val trackInfo = trackSelector!!.currentMappedTrackInfo
|
val trackInfo = trackSelector.currentMappedTrackInfo
|
||||||
?: return emptyList()
|
?: return emptyList()
|
||||||
val trackGroups = trackInfo.getTrackGroups(audioRendererIndex)
|
val trackGroups = trackInfo.getTrackGroups(audioRendererIndex)
|
||||||
for (i in 0 until trackGroups.length) {
|
for (i in 0 until trackGroups.length) {
|
||||||
|
@ -275,19 +272,19 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setAudioTrack(track: Int) {
|
fun setAudioTrack(track: Int) {
|
||||||
val trackInfo = trackSelector!!.currentMappedTrackInfo ?: return
|
val trackInfo = trackSelector.currentMappedTrackInfo ?: return
|
||||||
val trackGroups = trackInfo.getTrackGroups(audioRendererIndex)
|
val trackGroups = trackInfo.getTrackGroups(audioRendererIndex)
|
||||||
val override = SelectionOverride(track, 0)
|
val override = SelectionOverride(track, 0)
|
||||||
val rendererIndex = audioRendererIndex
|
val rendererIndex = audioRendererIndex
|
||||||
val params = trackSelector!!.buildUponParameters()
|
val params = trackSelector.buildUponParameters()
|
||||||
.setSelectionOverride(rendererIndex, trackGroups, override)
|
.setSelectionOverride(rendererIndex, trackGroups, override)
|
||||||
trackSelector!!.setParameters(params)
|
trackSelector.setParameters(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val audioRendererIndex: Int
|
private val audioRendererIndex: Int
|
||||||
get() {
|
get() {
|
||||||
for (i in 0 until exoPlayer!!.rendererCount) {
|
for (i in 0 until exoPlayer.rendererCount) {
|
||||||
if (exoPlayer!!.getRendererType(i) == C.TRACK_TYPE_AUDIO) {
|
if (exoPlayer.getRendererType(i) == C.TRACK_TYPE_AUDIO) {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -296,7 +293,7 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
|
||||||
|
|
||||||
val selectedAudioTrack: Int
|
val selectedAudioTrack: Int
|
||||||
get() {
|
get() {
|
||||||
val trackSelections = exoPlayer!!.currentTrackSelections
|
val trackSelections = exoPlayer.currentTrackSelections
|
||||||
val availableFormats = formats
|
val availableFormats = formats
|
||||||
for (i in 0 until trackSelections.length) {
|
for (i in 0 until trackSelections.length) {
|
||||||
val track = trackSelections[i] as ExoTrackSelection? ?: continue
|
val track = trackSelections[i] as ExoTrackSelection? ?: continue
|
||||||
|
@ -321,18 +318,18 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
|
||||||
|
|
||||||
val videoWidth: Int
|
val videoWidth: Int
|
||||||
get() {
|
get() {
|
||||||
if (exoPlayer!!.videoFormat == null) {
|
if (exoPlayer.videoFormat == null) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return exoPlayer!!.videoFormat!!.width
|
return exoPlayer.videoFormat!!.width
|
||||||
}
|
}
|
||||||
|
|
||||||
val videoHeight: Int
|
val videoHeight: Int
|
||||||
get() {
|
get() {
|
||||||
if (exoPlayer!!.videoFormat == null) {
|
if (exoPlayer.videoFormat == null) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return exoPlayer!!.videoFormat!!.height
|
return exoPlayer.videoFormat!!.height
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setOnBufferingUpdateListener(bufferingUpdateListener: Consumer<Int>?) {
|
fun setOnBufferingUpdateListener(bufferingUpdateListener: Consumer<Int>?) {
|
||||||
|
|
|
@ -108,6 +108,8 @@ import ac.mdiq.podvinci.storage.preferences.UserPreferences.showSkipOnFullNotifi
|
||||||
import ac.mdiq.podvinci.storage.preferences.UserPreferences.videoPlaybackSpeed
|
import ac.mdiq.podvinci.storage.preferences.UserPreferences.videoPlaybackSpeed
|
||||||
import ac.mdiq.podvinci.ui.appstartintent.MainActivityStarter
|
import ac.mdiq.podvinci.ui.appstartintent.MainActivityStarter
|
||||||
import ac.mdiq.podvinci.ui.appstartintent.VideoPlayerActivityStarter
|
import ac.mdiq.podvinci.ui.appstartintent.VideoPlayerActivityStarter
|
||||||
|
import android.os.Build.VERSION_CODES
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import io.reactivex.*
|
import io.reactivex.*
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
|
@ -127,11 +129,12 @@ import kotlin.math.max
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
class PlaybackService : MediaBrowserServiceCompat() {
|
class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
private var mediaPlayer: PlaybackServiceMediaPlayer? = null
|
private var mediaPlayer: PlaybackServiceMediaPlayer? = null
|
||||||
private var taskManager: PlaybackServiceTaskManager? = null
|
|
||||||
private var stateManager: PlaybackServiceStateManager? = null
|
|
||||||
private var positionEventTimer: Disposable? = null
|
private var positionEventTimer: Disposable? = null
|
||||||
private var notificationBuilder: PlaybackServiceNotificationBuilder? = null
|
|
||||||
private var castStateListener: CastStateListener? = null
|
private lateinit var taskManager: PlaybackServiceTaskManager
|
||||||
|
private lateinit var stateManager: PlaybackServiceStateManager
|
||||||
|
private lateinit var notificationBuilder: PlaybackServiceNotificationBuilder
|
||||||
|
private lateinit var castStateListener: CastStateListener
|
||||||
|
|
||||||
private var autoSkippedFeedMediaId: String? = null
|
private var autoSkippedFeedMediaId: String? = null
|
||||||
private var clickCount = 0
|
private var clickCount = 0
|
||||||
|
@ -162,9 +165,19 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
stateManager = PlaybackServiceStateManager(this)
|
stateManager = PlaybackServiceStateManager(this)
|
||||||
notificationBuilder = PlaybackServiceNotificationBuilder(this)
|
notificationBuilder = PlaybackServiceNotificationBuilder(this)
|
||||||
|
|
||||||
|
// TODO: this shit doesn't work
|
||||||
|
// if (Build.VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) {
|
||||||
|
// registerReceiver(autoStateUpdated, IntentFilter("com.google.android.gms.car.media.STATUS"), RECEIVER_EXPORTED)
|
||||||
|
// registerReceiver(shutdownReceiver, IntentFilter(PlaybackServiceInterface.ACTION_SHUTDOWN_PLAYBACK_SERVICE), RECEIVER_EXPORTED)
|
||||||
|
// } else {
|
||||||
|
// ContextCompat.registerReceiver(applicationContext, autoStateUpdated, IntentFilter("com.google.android.gms.car.media.STATUS"), ContextCompat.RECEIVER_EXPORTED)
|
||||||
|
// ContextCompat.registerReceiver(applicationContext, shutdownReceiver, IntentFilter(PlaybackServiceInterface.ACTION_SHUTDOWN_PLAYBACK_SERVICE), ContextCompat.RECEIVER_EXPORTED)
|
||||||
|
// }
|
||||||
|
|
||||||
registerReceiver(autoStateUpdated, IntentFilter("com.google.android.gms.car.media.STATUS"))
|
registerReceiver(autoStateUpdated, IntentFilter("com.google.android.gms.car.media.STATUS"))
|
||||||
registerReceiver(headsetDisconnected, IntentFilter(Intent.ACTION_HEADSET_PLUG))
|
|
||||||
registerReceiver(shutdownReceiver, IntentFilter(PlaybackServiceInterface.ACTION_SHUTDOWN_PLAYBACK_SERVICE))
|
registerReceiver(shutdownReceiver, IntentFilter(PlaybackServiceInterface.ACTION_SHUTDOWN_PLAYBACK_SERVICE))
|
||||||
|
|
||||||
|
registerReceiver(headsetDisconnected, IntentFilter(Intent.ACTION_HEADSET_PLUG))
|
||||||
registerReceiver(bluetoothStateUpdated, IntentFilter(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED))
|
registerReceiver(bluetoothStateUpdated, IntentFilter(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED))
|
||||||
registerReceiver(audioBecomingNoisy, IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY))
|
registerReceiver(audioBecomingNoisy, IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY))
|
||||||
EventBus.getDefault().register(this)
|
EventBus.getDefault().register(this)
|
||||||
|
@ -235,8 +248,8 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
Log.d(TAG, "Service is about to be destroyed")
|
Log.d(TAG, "Service is about to be destroyed")
|
||||||
|
|
||||||
if (notificationBuilder!!.playerStatus == PlayerStatus.PLAYING) {
|
if (notificationBuilder.playerStatus == PlayerStatus.PLAYING) {
|
||||||
notificationBuilder!!.playerStatus = PlayerStatus.STOPPED
|
notificationBuilder.playerStatus = PlayerStatus.STOPPED
|
||||||
val notificationManager = NotificationManagerCompat.from(this)
|
val notificationManager = NotificationManagerCompat.from(this)
|
||||||
if (ActivityCompat.checkSelfPermission(this,
|
if (ActivityCompat.checkSelfPermission(this,
|
||||||
Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
@ -249,25 +262,24 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
// for ActivityCompat#requestPermissions for more details.
|
// for ActivityCompat#requestPermissions for more details.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
notificationManager.notify(R.id.notification_playing, notificationBuilder!!.build())
|
notificationManager.notify(R.id.notification_playing, notificationBuilder.build())
|
||||||
}
|
}
|
||||||
stateManager!!.stopForeground(!isPersistNotify)
|
stateManager.stopForeground(!isPersistNotify)
|
||||||
isRunning = false
|
isRunning = false
|
||||||
currentMediaType = MediaType.UNKNOWN
|
currentMediaType = MediaType.UNKNOWN
|
||||||
castStateListener!!.destroy()
|
castStateListener.destroy()
|
||||||
|
|
||||||
cancelPositionObserver()
|
cancelPositionObserver()
|
||||||
if (mediaSession != null) {
|
mediaSession?.release()
|
||||||
mediaSession!!.release()
|
mediaSession = null
|
||||||
mediaSession = null
|
|
||||||
}
|
|
||||||
unregisterReceiver(autoStateUpdated)
|
unregisterReceiver(autoStateUpdated)
|
||||||
unregisterReceiver(headsetDisconnected)
|
unregisterReceiver(headsetDisconnected)
|
||||||
unregisterReceiver(shutdownReceiver)
|
unregisterReceiver(shutdownReceiver)
|
||||||
unregisterReceiver(bluetoothStateUpdated)
|
unregisterReceiver(bluetoothStateUpdated)
|
||||||
unregisterReceiver(audioBecomingNoisy)
|
unregisterReceiver(audioBecomingNoisy)
|
||||||
mediaPlayer!!.shutdown()
|
mediaPlayer?.shutdown()
|
||||||
taskManager!!.shutdown()
|
taskManager.shutdown()
|
||||||
EventBus.getDefault().unregister(this)
|
EventBus.getDefault().unregister(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -428,7 +440,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
super.onStartCommand(intent, flags, startId)
|
super.onStartCommand(intent, flags, startId)
|
||||||
Log.d(TAG, "OnStartCommand called")
|
Log.d(TAG, "OnStartCommand called")
|
||||||
|
|
||||||
stateManager!!.startForeground(R.id.notification_playing, notificationBuilder!!.build())
|
stateManager.startForeground(R.id.notification_playing, notificationBuilder.build())
|
||||||
val notificationManager = NotificationManagerCompat.from(this)
|
val notificationManager = NotificationManagerCompat.from(this)
|
||||||
notificationManager.cancel(R.id.notification_streaming_confirmation)
|
notificationManager.cancel(R.id.notification_streaming_confirmation)
|
||||||
|
|
||||||
|
@ -438,13 +450,13 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
val playable = intent.getParcelableExtra<Playable>(PlaybackServiceInterface.EXTRA_PLAYABLE)
|
val playable = intent.getParcelableExtra<Playable>(PlaybackServiceInterface.EXTRA_PLAYABLE)
|
||||||
if (keycode == -1 && playable == null && customAction == null) {
|
if (keycode == -1 && playable == null && customAction == null) {
|
||||||
Log.e(TAG, "PlaybackService was started with no arguments")
|
Log.e(TAG, "PlaybackService was started with no arguments")
|
||||||
stateManager!!.stopService()
|
stateManager.stopService()
|
||||||
return START_NOT_STICKY
|
return START_NOT_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((flags and START_FLAG_REDELIVERY) != 0) {
|
if ((flags and START_FLAG_REDELIVERY) != 0) {
|
||||||
Log.d(TAG, "onStartCommand is a redelivered intent, calling stopForeground now.")
|
Log.d(TAG, "onStartCommand is a redelivered intent, calling stopForeground now.")
|
||||||
stateManager!!.stopForeground(true)
|
stateManager.stopForeground(true)
|
||||||
} else {
|
} else {
|
||||||
if (keycode != -1) {
|
if (keycode != -1) {
|
||||||
val notificationButton: Boolean
|
val notificationButton: Boolean
|
||||||
|
@ -456,12 +468,12 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
notificationButton = true
|
notificationButton = true
|
||||||
}
|
}
|
||||||
val handled = handleKeycode(keycode, notificationButton)
|
val handled = handleKeycode(keycode, notificationButton)
|
||||||
if (!handled && !stateManager!!.hasReceivedValidStartCommand()) {
|
if (!handled && !stateManager.hasReceivedValidStartCommand()) {
|
||||||
stateManager!!.stopService()
|
stateManager.stopService()
|
||||||
return START_NOT_STICKY
|
return START_NOT_STICKY
|
||||||
}
|
}
|
||||||
} else if (playable != null) {
|
} else if (playable != null) {
|
||||||
stateManager!!.validStartCommandWasReceived()
|
stateManager.validStartCommandWasReceived()
|
||||||
val allowStreamThisTime = intent.getBooleanExtra(
|
val allowStreamThisTime = intent.getBooleanExtra(
|
||||||
PlaybackServiceInterface.EXTRA_ALLOW_STREAM_THIS_TIME, false)
|
PlaybackServiceInterface.EXTRA_ALLOW_STREAM_THIS_TIME, false)
|
||||||
val allowStreamAlways = intent.getBooleanExtra(
|
val allowStreamAlways = intent.getBooleanExtra(
|
||||||
|
@ -484,11 +496,11 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
{ error: Throwable ->
|
{ error: Throwable ->
|
||||||
Log.d(TAG, "Playable was not found. Stopping service.")
|
Log.d(TAG, "Playable was not found. Stopping service.")
|
||||||
error.printStackTrace()
|
error.printStackTrace()
|
||||||
stateManager!!.stopService()
|
stateManager.stopService()
|
||||||
})
|
})
|
||||||
return START_NOT_STICKY
|
return START_NOT_STICKY
|
||||||
} else {
|
} else {
|
||||||
mediaSession!!.controller.transportControls.sendCustomAction(customAction, null)
|
mediaSession?.controller?.transportControls?.sendCustomAction(customAction, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -508,7 +520,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
val duration = duration
|
val duration = duration
|
||||||
if (skipIntro * 1000 < duration || duration <= 0) {
|
if (skipIntro * 1000 < duration || duration <= 0) {
|
||||||
Log.d(TAG, "skipIntro " + playable.getEpisodeTitle())
|
Log.d(TAG, "skipIntro " + playable.getEpisodeTitle())
|
||||||
mediaPlayer!!.seekTo(skipIntro * 1000)
|
mediaPlayer?.seekTo(skipIntro * 1000)
|
||||||
val skipIntroMesg = context.getString(R.string.pref_feed_skip_intro_toast,
|
val skipIntroMesg = context.getString(R.string.pref_feed_skip_intro_toast,
|
||||||
skipIntro)
|
skipIntro)
|
||||||
val toast = Toast.makeText(context, skipIntroMesg,
|
val toast = Toast.makeText(context, skipIntroMesg,
|
||||||
|
@ -529,7 +541,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
val intentAllowThisTime = Intent(originalIntent)
|
val intentAllowThisTime = Intent(originalIntent)
|
||||||
intentAllowThisTime.setAction(PlaybackServiceInterface.EXTRA_ALLOW_STREAM_THIS_TIME)
|
intentAllowThisTime.setAction(PlaybackServiceInterface.EXTRA_ALLOW_STREAM_THIS_TIME)
|
||||||
intentAllowThisTime.putExtra(PlaybackServiceInterface.EXTRA_ALLOW_STREAM_THIS_TIME, true)
|
intentAllowThisTime.putExtra(PlaybackServiceInterface.EXTRA_ALLOW_STREAM_THIS_TIME, true)
|
||||||
val pendingIntentAllowThisTime = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
val pendingIntentAllowThisTime = if (Build.VERSION.SDK_INT >= VERSION_CODES.O) {
|
||||||
PendingIntent.getForegroundService(this,
|
PendingIntent.getForegroundService(this,
|
||||||
R.id.pending_intent_allow_stream_this_time, intentAllowThisTime,
|
R.id.pending_intent_allow_stream_this_time, intentAllowThisTime,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||||
|
@ -542,7 +554,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
val intentAlwaysAllow = Intent(intentAllowThisTime)
|
val intentAlwaysAllow = Intent(intentAllowThisTime)
|
||||||
intentAlwaysAllow.setAction(PlaybackServiceInterface.EXTRA_ALLOW_STREAM_ALWAYS)
|
intentAlwaysAllow.setAction(PlaybackServiceInterface.EXTRA_ALLOW_STREAM_ALWAYS)
|
||||||
intentAlwaysAllow.putExtra(PlaybackServiceInterface.EXTRA_ALLOW_STREAM_ALWAYS, true)
|
intentAlwaysAllow.putExtra(PlaybackServiceInterface.EXTRA_ALLOW_STREAM_ALWAYS, true)
|
||||||
val pendingIntentAlwaysAllow = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
val pendingIntentAlwaysAllow = if (Build.VERSION.SDK_INT >= VERSION_CODES.O) {
|
||||||
PendingIntent.getForegroundService(this,
|
PendingIntent.getForegroundService(this,
|
||||||
R.id.pending_intent_allow_stream_always, intentAlwaysAllow,
|
R.id.pending_intent_allow_stream_always, intentAlwaysAllow,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||||
|
@ -589,44 +601,56 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
*/
|
*/
|
||||||
private fun handleKeycode(keycode: Int, notificationButton: Boolean): Boolean {
|
private fun handleKeycode(keycode: Int, notificationButton: Boolean): Boolean {
|
||||||
Log.d(TAG, "Handling keycode: $keycode")
|
Log.d(TAG, "Handling keycode: $keycode")
|
||||||
val info = mediaPlayer!!.pSMPInfo
|
val info = mediaPlayer?.pSMPInfo
|
||||||
val status = info.playerStatus
|
val status = info?.playerStatus
|
||||||
when (keycode) {
|
when (keycode) {
|
||||||
KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> {
|
KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> {
|
||||||
if (status == PlayerStatus.PLAYING) {
|
when {
|
||||||
mediaPlayer!!.pause(!isPersistNotify, false)
|
status == PlayerStatus.PLAYING -> {
|
||||||
} else if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED) {
|
mediaPlayer?.pause(!isPersistNotify, false)
|
||||||
mediaPlayer!!.resume()
|
}
|
||||||
} else if (status == PlayerStatus.PREPARING) {
|
status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED -> {
|
||||||
mediaPlayer!!.setStartWhenPrepared(!mediaPlayer!!.isStartWhenPrepared())
|
mediaPlayer?.resume()
|
||||||
} else if (status == PlayerStatus.INITIALIZED) {
|
}
|
||||||
mediaPlayer!!.setStartWhenPrepared(true)
|
status == PlayerStatus.PREPARING -> {
|
||||||
mediaPlayer!!.prepare()
|
mediaPlayer?.setStartWhenPrepared(!mediaPlayer!!.isStartWhenPrepared())
|
||||||
} else if (mediaPlayer!!.getPlayable() == null) {
|
}
|
||||||
startPlayingFromPreferences()
|
status == PlayerStatus.INITIALIZED -> {
|
||||||
} else {
|
mediaPlayer?.setStartWhenPrepared(true)
|
||||||
return false
|
mediaPlayer?.prepare()
|
||||||
|
}
|
||||||
|
mediaPlayer?.getPlayable() == null -> {
|
||||||
|
startPlayingFromPreferences()
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
taskManager!!.restartSleepTimer()
|
taskManager.restartSleepTimer()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
KeyEvent.KEYCODE_MEDIA_PLAY -> {
|
KeyEvent.KEYCODE_MEDIA_PLAY -> {
|
||||||
if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED) {
|
when {
|
||||||
mediaPlayer!!.resume()
|
status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED -> {
|
||||||
} else if (status == PlayerStatus.INITIALIZED) {
|
mediaPlayer?.resume()
|
||||||
mediaPlayer!!.setStartWhenPrepared(true)
|
}
|
||||||
mediaPlayer!!.prepare()
|
status == PlayerStatus.INITIALIZED -> {
|
||||||
} else if (mediaPlayer!!.getPlayable() == null) {
|
mediaPlayer?.setStartWhenPrepared(true)
|
||||||
startPlayingFromPreferences()
|
mediaPlayer?.prepare()
|
||||||
} else {
|
}
|
||||||
return false
|
mediaPlayer?.getPlayable() == null -> {
|
||||||
|
startPlayingFromPreferences()
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
taskManager!!.restartSleepTimer()
|
taskManager.restartSleepTimer()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
KeyEvent.KEYCODE_MEDIA_PAUSE -> {
|
KeyEvent.KEYCODE_MEDIA_PAUSE -> {
|
||||||
if (status == PlayerStatus.PLAYING) {
|
if (status == PlayerStatus.PLAYING) {
|
||||||
mediaPlayer!!.pause(!isPersistNotify, false)
|
mediaPlayer?.pause(!isPersistNotify, false)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -636,14 +660,14 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
// Handle remapped button as notification button which is not remapped again.
|
// Handle remapped button as notification button which is not remapped again.
|
||||||
return handleKeycode(hardwareForwardButton, true)
|
return handleKeycode(hardwareForwardButton, true)
|
||||||
} else if (this.status == PlayerStatus.PLAYING || this.status == PlayerStatus.PAUSED) {
|
} else if (this.status == PlayerStatus.PLAYING || this.status == PlayerStatus.PAUSED) {
|
||||||
mediaPlayer!!.skip()
|
mediaPlayer?.skip()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
KeyEvent.KEYCODE_MEDIA_FAST_FORWARD -> {
|
KeyEvent.KEYCODE_MEDIA_FAST_FORWARD -> {
|
||||||
if (this.status == PlayerStatus.PLAYING || this.status == PlayerStatus.PAUSED) {
|
if (this.status == PlayerStatus.PLAYING || this.status == PlayerStatus.PAUSED) {
|
||||||
mediaPlayer!!.seekDelta(fastForwardSecs * 1000)
|
mediaPlayer?.seekDelta(fastForwardSecs * 1000)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -653,29 +677,29 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
// Handle remapped button as notification button which is not remapped again.
|
// Handle remapped button as notification button which is not remapped again.
|
||||||
return handleKeycode(hardwarePreviousButton, true)
|
return handleKeycode(hardwarePreviousButton, true)
|
||||||
} else if (this.status == PlayerStatus.PLAYING || this.status == PlayerStatus.PAUSED) {
|
} else if (this.status == PlayerStatus.PLAYING || this.status == PlayerStatus.PAUSED) {
|
||||||
mediaPlayer!!.seekTo(0)
|
mediaPlayer?.seekTo(0)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
KeyEvent.KEYCODE_MEDIA_REWIND -> {
|
KeyEvent.KEYCODE_MEDIA_REWIND -> {
|
||||||
if (this.status == PlayerStatus.PLAYING || this.status == PlayerStatus.PAUSED) {
|
if (this.status == PlayerStatus.PLAYING || this.status == PlayerStatus.PAUSED) {
|
||||||
mediaPlayer!!.seekDelta(-rewindSecs * 1000)
|
mediaPlayer?.seekDelta(-rewindSecs * 1000)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
KeyEvent.KEYCODE_MEDIA_STOP -> {
|
KeyEvent.KEYCODE_MEDIA_STOP -> {
|
||||||
if (status == PlayerStatus.PLAYING) {
|
if (status == PlayerStatus.PLAYING) {
|
||||||
mediaPlayer!!.pause(true, true)
|
mediaPlayer?.pause(true, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
stateManager!!.stopForeground(true) // gets rid of persistent notification
|
stateManager.stopForeground(true) // gets rid of persistent notification
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
Log.d(TAG, "Unhandled key code: $keycode")
|
Log.d(TAG, "Unhandled key code: $keycode")
|
||||||
if (info.playable != null && info.playerStatus == PlayerStatus.PLAYING) { // only notify the user about an unknown key event if it is actually doing something
|
if (info?.playable != null && info?.playerStatus == PlayerStatus.PLAYING) { // only notify the user about an unknown key event if it is actually doing something
|
||||||
val message = String.format(resources.getString(R.string.unknown_media_key), keycode)
|
val message = String.format(resources.getString(R.string.unknown_media_key), keycode)
|
||||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
@ -696,7 +720,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
{ error: Throwable ->
|
{ error: Throwable ->
|
||||||
Log.d(TAG, "Playable was not loaded from preferences. Stopping service.")
|
Log.d(TAG, "Playable was not loaded from preferences. Stopping service.")
|
||||||
error.printStackTrace()
|
error.printStackTrace()
|
||||||
stateManager!!.stopService()
|
stateManager.stopService()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -708,7 +732,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
PlaybackServiceStarter(this, playable)
|
PlaybackServiceStarter(this, playable)
|
||||||
.intent)
|
.intent)
|
||||||
writeNoMediaPlaying()
|
writeNoMediaPlaying()
|
||||||
stateManager!!.stopService()
|
stateManager.stopService()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -716,9 +740,9 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
clearCurrentlyPlayingTemporaryPlaybackSpeed()
|
clearCurrentlyPlayingTemporaryPlaybackSpeed()
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaPlayer!!.playMediaObject(playable, stream, true, true)
|
mediaPlayer?.playMediaObject(playable, stream, true, true)
|
||||||
stateManager!!.validStartCommandWasReceived()
|
stateManager.validStartCommandWasReceived()
|
||||||
stateManager!!.startForeground(R.id.notification_playing, notificationBuilder!!.build())
|
stateManager.startForeground(R.id.notification_playing, notificationBuilder.build())
|
||||||
recreateMediaSessionIfNeeded()
|
recreateMediaSessionIfNeeded()
|
||||||
updateNotificationAndMediaSession(playable)
|
updateNotificationAndMediaSession(playable)
|
||||||
addPlayableToQueue(playable)
|
addPlayableToQueue(playable)
|
||||||
|
@ -730,14 +754,14 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
*/
|
*/
|
||||||
fun setVideoSurface(sh: SurfaceHolder?) {
|
fun setVideoSurface(sh: SurfaceHolder?) {
|
||||||
Log.d(TAG, "Setting display")
|
Log.d(TAG, "Setting display")
|
||||||
mediaPlayer!!.setVideoSurface(sh)
|
mediaPlayer?.setVideoSurface(sh)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun notifyVideoSurfaceAbandoned() {
|
fun notifyVideoSurfaceAbandoned() {
|
||||||
mediaPlayer!!.pause(true, false)
|
mediaPlayer?.pause(true, false)
|
||||||
mediaPlayer!!.resetVideoSurface()
|
mediaPlayer?.resetVideoSurface()
|
||||||
updateNotificationAndMediaSession(playable)
|
updateNotificationAndMediaSession(playable)
|
||||||
stateManager!!.stopForeground(!isPersistNotify)
|
stateManager.stopForeground(!isPersistNotify)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val taskManagerCallback: PSTMCallback = object : PSTMCallback {
|
private val taskManagerCallback: PSTMCallback = object : PSTMCallback {
|
||||||
|
@ -752,7 +776,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
|
|
||||||
override fun onChapterLoaded(media: Playable?) {
|
override fun onChapterLoaded(media: Playable?) {
|
||||||
sendNotificationBroadcast(PlaybackServiceInterface.NOTIFICATION_TYPE_RELOAD, 0)
|
sendNotificationBroadcast(PlaybackServiceInterface.NOTIFICATION_TYPE_RELOAD, 0)
|
||||||
updateMediaSession(mediaPlayer!!.playerStatus)
|
updateMediaSession(mediaPlayer?.playerStatus)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -767,36 +791,34 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
updateMediaSession(newInfo!!.playerStatus)
|
updateMediaSession(newInfo!!.playerStatus)
|
||||||
when (newInfo.playerStatus) {
|
when (newInfo.playerStatus) {
|
||||||
PlayerStatus.INITIALIZED -> {
|
PlayerStatus.INITIALIZED -> {
|
||||||
if (mediaPlayer!!.pSMPInfo.playable != null) {
|
if (mediaPlayer != null) {
|
||||||
writeMediaPlaying(mediaPlayer!!.pSMPInfo.playable,
|
writeMediaPlaying(mediaPlayer!!.pSMPInfo.playable, mediaPlayer!!.pSMPInfo.playerStatus)
|
||||||
mediaPlayer!!.pSMPInfo.playerStatus)
|
|
||||||
}
|
}
|
||||||
updateNotificationAndMediaSession(newInfo.playable)
|
updateNotificationAndMediaSession(newInfo.playable)
|
||||||
}
|
}
|
||||||
PlayerStatus.PREPARED -> {
|
PlayerStatus.PREPARED -> {
|
||||||
if (mediaPlayer!!.pSMPInfo.playable != null) {
|
if (mediaPlayer != null) {
|
||||||
writeMediaPlaying(mediaPlayer!!.pSMPInfo.playable,
|
writeMediaPlaying(mediaPlayer!!.pSMPInfo.playable, mediaPlayer!!.pSMPInfo.playerStatus)
|
||||||
mediaPlayer!!.pSMPInfo.playerStatus)
|
|
||||||
}
|
}
|
||||||
taskManager!!.startChapterLoader(newInfo.playable!!)
|
taskManager.startChapterLoader(newInfo.playable!!)
|
||||||
}
|
}
|
||||||
PlayerStatus.PAUSED -> {
|
PlayerStatus.PAUSED -> {
|
||||||
updateNotificationAndMediaSession(newInfo.playable)
|
updateNotificationAndMediaSession(newInfo.playable)
|
||||||
if (!isCasting) {
|
if (!isCasting) {
|
||||||
stateManager!!.stopForeground(!isPersistNotify)
|
stateManager.stopForeground(!isPersistNotify)
|
||||||
}
|
}
|
||||||
cancelPositionObserver()
|
cancelPositionObserver()
|
||||||
writePlayerStatus(mediaPlayer!!.playerStatus)
|
if (mediaPlayer != null) writePlayerStatus(mediaPlayer!!.playerStatus)
|
||||||
}
|
}
|
||||||
PlayerStatus.STOPPED -> {}
|
PlayerStatus.STOPPED -> {}
|
||||||
PlayerStatus.PLAYING -> {
|
PlayerStatus.PLAYING -> {
|
||||||
writePlayerStatus(mediaPlayer!!.playerStatus)
|
if (mediaPlayer != null) writePlayerStatus(mediaPlayer!!.playerStatus)
|
||||||
saveCurrentPosition(true, null, Playable.INVALID_TIME)
|
saveCurrentPosition(true, null, Playable.INVALID_TIME)
|
||||||
recreateMediaSessionIfNeeded()
|
recreateMediaSessionIfNeeded()
|
||||||
updateNotificationAndMediaSession(newInfo.playable)
|
updateNotificationAndMediaSession(newInfo.playable)
|
||||||
setupPositionObserver()
|
setupPositionObserver()
|
||||||
stateManager!!.validStartCommandWasReceived()
|
stateManager.validStartCommandWasReceived()
|
||||||
stateManager!!.startForeground(R.id.notification_playing, notificationBuilder!!.build())
|
stateManager.startForeground(R.id.notification_playing, notificationBuilder.build())
|
||||||
// set sleep timer if auto-enabled
|
// set sleep timer if auto-enabled
|
||||||
var autoEnableByTime = true
|
var autoEnableByTime = true
|
||||||
val fromSetting = autoEnableFrom()
|
val fromSetting = autoEnableFrom()
|
||||||
|
@ -817,11 +839,11 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
}
|
}
|
||||||
PlayerStatus.ERROR -> {
|
PlayerStatus.ERROR -> {
|
||||||
writeNoMediaPlaying()
|
writeNoMediaPlaying()
|
||||||
stateManager!!.stopService()
|
stateManager.stopService()
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
if (Build.VERSION.SDK_INT >= VERSION_CODES.N) {
|
||||||
TileService.requestListeningState(applicationContext,
|
TileService.requestListeningState(applicationContext,
|
||||||
ComponentName(applicationContext, QuickSettingsTileService::class.java))
|
ComponentName(applicationContext, QuickSettingsTileService::class.java))
|
||||||
}
|
}
|
||||||
|
@ -829,11 +851,11 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
sendLocalBroadcast(applicationContext, ACTION_PLAYER_STATUS_CHANGED)
|
sendLocalBroadcast(applicationContext, ACTION_PLAYER_STATUS_CHANGED)
|
||||||
bluetoothNotifyChange(newInfo, AVRCP_ACTION_PLAYER_STATUS_CHANGED)
|
bluetoothNotifyChange(newInfo, AVRCP_ACTION_PLAYER_STATUS_CHANGED)
|
||||||
bluetoothNotifyChange(newInfo, AVRCP_ACTION_META_CHANGED)
|
bluetoothNotifyChange(newInfo, AVRCP_ACTION_META_CHANGED)
|
||||||
taskManager!!.requestWidgetUpdate()
|
taskManager.requestWidgetUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun shouldStop() {
|
override fun shouldStop() {
|
||||||
stateManager!!.stopForeground(!isPersistNotify)
|
stateManager.stopForeground(!isPersistNotify)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMediaChanged(reloadUI: Boolean) {
|
override fun onMediaChanged(reloadUI: Boolean) {
|
||||||
|
@ -851,21 +873,21 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPlaybackStart(playable: Playable, position: Int) {
|
override fun onPlaybackStart(playable: Playable, position: Int) {
|
||||||
taskManager!!.startWidgetUpdater()
|
taskManager.startWidgetUpdater()
|
||||||
if (position != Playable.INVALID_TIME) {
|
if (position != Playable.INVALID_TIME) {
|
||||||
playable.setPosition(position)
|
playable.setPosition(position)
|
||||||
} else {
|
} else {
|
||||||
skipIntro(playable)
|
skipIntro(playable)
|
||||||
}
|
}
|
||||||
playable.onPlaybackStart()
|
playable.onPlaybackStart()
|
||||||
taskManager!!.startPositionSaver()
|
taskManager.startPositionSaver()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPlaybackPause(playable: Playable?, position: Int) {
|
override fun onPlaybackPause(playable: Playable?, position: Int) {
|
||||||
taskManager!!.cancelPositionSaver()
|
taskManager.cancelPositionSaver()
|
||||||
cancelPositionObserver()
|
cancelPositionObserver()
|
||||||
saveCurrentPosition(position == Playable.INVALID_TIME || playable == null, playable, position)
|
saveCurrentPosition(position == Playable.INVALID_TIME || playable == null, playable, position)
|
||||||
taskManager!!.cancelWidgetUpdater()
|
taskManager.cancelWidgetUpdater()
|
||||||
if (playable != null) {
|
if (playable != null) {
|
||||||
if (playable is FeedMedia) {
|
if (playable is FeedMedia) {
|
||||||
SynchronizationQueueSink.enqueueEpisodePlayedIfSynchronizationIsActive(applicationContext, playable, false)
|
SynchronizationQueueSink.enqueueEpisodePlayedIfSynchronizationIsActive(applicationContext, playable, false)
|
||||||
|
@ -897,10 +919,10 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun playerError(event: PlayerErrorEvent?) {
|
fun playerError(event: PlayerErrorEvent?) {
|
||||||
if (mediaPlayer!!.playerStatus == PlayerStatus.PLAYING) {
|
if (mediaPlayer?.playerStatus == PlayerStatus.PLAYING) {
|
||||||
mediaPlayer!!.pause(true, false)
|
mediaPlayer!!.pause(true, false)
|
||||||
}
|
}
|
||||||
stateManager!!.stopService()
|
stateManager.stopService()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
|
@ -908,7 +930,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
fun bufferUpdate(event: BufferUpdateEvent) {
|
fun bufferUpdate(event: BufferUpdateEvent) {
|
||||||
if (event.hasEnded()) {
|
if (event.hasEnded()) {
|
||||||
val playable = playable
|
val playable = playable
|
||||||
if (this.playable is FeedMedia && playable!!.getDuration() <= 0 && mediaPlayer!!.getDuration() > 0) {
|
if (this.playable is FeedMedia && playable!!.getDuration() <= 0 && (mediaPlayer?.getDuration()?:0) > 0) {
|
||||||
// Playable is being streamed and does not have a duration specified in the feed
|
// Playable is being streamed and does not have a duration specified in the feed
|
||||||
playable.setDuration(mediaPlayer!!.getDuration())
|
playable.setDuration(mediaPlayer!!.getDuration())
|
||||||
DBWriter.setFeedMedia(playable as FeedMedia?)
|
DBWriter.setFeedMedia(playable as FeedMedia?)
|
||||||
|
@ -921,16 +943,16 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun sleepTimerUpdate(event: SleepTimerUpdatedEvent) {
|
fun sleepTimerUpdate(event: SleepTimerUpdatedEvent) {
|
||||||
if (event.isOver) {
|
if (event.isOver) {
|
||||||
mediaPlayer!!.pause(true, true)
|
mediaPlayer?.pause(true, true)
|
||||||
mediaPlayer!!.setVolume(1.0f, 1.0f)
|
mediaPlayer?.setVolume(1.0f, 1.0f)
|
||||||
} else if (event.getTimeLeft() < PlaybackServiceTaskManager.NOTIFICATION_THRESHOLD) {
|
} else if (event.getTimeLeft() < PlaybackServiceTaskManager.NOTIFICATION_THRESHOLD) {
|
||||||
val multiplicators = floatArrayOf(0.1f, 0.2f, 0.3f, 0.3f, 0.3f, 0.4f, 0.4f, 0.4f, 0.6f, 0.8f)
|
val multiplicators = floatArrayOf(0.1f, 0.2f, 0.3f, 0.3f, 0.3f, 0.4f, 0.4f, 0.4f, 0.6f, 0.8f)
|
||||||
val multiplicator = multiplicators[max(0.0, (event.getTimeLeft().toInt() / 1000).toDouble())
|
val multiplicator = multiplicators[max(0.0, (event.getTimeLeft().toInt() / 1000).toDouble())
|
||||||
.toInt()]
|
.toInt()]
|
||||||
Log.d(TAG, "onSleepTimerAlmostExpired: $multiplicator")
|
Log.d(TAG, "onSleepTimerAlmostExpired: $multiplicator")
|
||||||
mediaPlayer!!.setVolume(multiplicator, multiplicator)
|
mediaPlayer?.setVolume(multiplicator, multiplicator)
|
||||||
} else if (event.isCancelled) {
|
} else if (event.isCancelled) {
|
||||||
mediaPlayer!!.setVolume(1.0f, 1.0f)
|
mediaPlayer?.setVolume(1.0f, 1.0f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -967,7 +989,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
if (!nextItem.media!!.localFileAvailable() && !isStreamingAllowed && isFollowQueue && nextItem.feed != null && !nextItem.feed!!.isLocalFeed) {
|
if (!nextItem.media!!.localFileAvailable() && !isStreamingAllowed && isFollowQueue && nextItem.feed != null && !nextItem.feed!!.isLocalFeed) {
|
||||||
displayStreamingNotAllowedNotification(PlaybackServiceStarter(this, nextItem.media!!).intent)
|
displayStreamingNotAllowedNotification(PlaybackServiceStarter(this, nextItem.media!!).intent)
|
||||||
writeNoMediaPlaying()
|
writeNoMediaPlaying()
|
||||||
stateManager!!.stopService()
|
stateManager.stopService()
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return nextItem.media
|
return nextItem.media
|
||||||
|
@ -980,11 +1002,11 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
Log.d(TAG, "Playback ended")
|
Log.d(TAG, "Playback ended")
|
||||||
clearCurrentlyPlayingTemporaryPlaybackSpeed()
|
clearCurrentlyPlayingTemporaryPlaybackSpeed()
|
||||||
if (stopPlaying) {
|
if (stopPlaying) {
|
||||||
taskManager!!.cancelPositionSaver()
|
taskManager.cancelPositionSaver()
|
||||||
cancelPositionObserver()
|
cancelPositionObserver()
|
||||||
if (!isCasting) {
|
if (!isCasting) {
|
||||||
stateManager!!.stopForeground(true)
|
stateManager.stopForeground(true)
|
||||||
stateManager!!.stopService()
|
stateManager.stopService()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mediaType == null) {
|
if (mediaType == null) {
|
||||||
|
@ -1084,11 +1106,11 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
|
|
||||||
fun setSleepTimer(waitingTime: Long) {
|
fun setSleepTimer(waitingTime: Long) {
|
||||||
Log.d(TAG, "Setting sleep timer to $waitingTime milliseconds")
|
Log.d(TAG, "Setting sleep timer to $waitingTime milliseconds")
|
||||||
taskManager!!.setSleepTimer(waitingTime)
|
taskManager.setSleepTimer(waitingTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun disableSleepTimer() {
|
fun disableSleepTimer() {
|
||||||
taskManager!!.disableSleepTimer()
|
taskManager.disableSleepTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendNotificationBroadcast(type: Int, code: Int) {
|
private fun sendNotificationBroadcast(type: Int, code: Int) {
|
||||||
|
@ -1100,7 +1122,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun skipEndingIfNecessary() {
|
private fun skipEndingIfNecessary() {
|
||||||
val playable = mediaPlayer!!.getPlayable() as? FeedMedia ?: return
|
val playable = mediaPlayer?.getPlayable() as? FeedMedia ?: return
|
||||||
|
|
||||||
val duration = duration
|
val duration = duration
|
||||||
val remainingTime = duration - currentPosition
|
val remainingTime = duration - currentPosition
|
||||||
|
@ -1227,8 +1249,8 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
builder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, p.getFeedTitle())
|
builder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, p.getFeedTitle())
|
||||||
|
|
||||||
|
|
||||||
if (notificationBuilder!!.isIconCached) {
|
if (notificationBuilder.isIconCached) {
|
||||||
builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, notificationBuilder!!.cachedIcon)
|
builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, notificationBuilder.cachedIcon)
|
||||||
} else {
|
} else {
|
||||||
var iconUri = p.getImageLocation()
|
var iconUri = p.getImageLocation()
|
||||||
if (p is FeedMedia) { // Don't use embedded cover etc, which Android can't load
|
if (p is FeedMedia) { // Don't use embedded cover etc, which Android can't load
|
||||||
|
@ -1247,7 +1269,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stateManager!!.hasReceivedValidStartCommand()) {
|
if (stateManager.hasReceivedValidStartCommand()) {
|
||||||
mediaSession!!.setSessionActivity(PendingIntent.getActivity(this, R.id.pending_intent_player_activity,
|
mediaSession!!.setSessionActivity(PendingIntent.getActivity(this, R.id.pending_intent_player_activity,
|
||||||
getPlayerActivityIntent(this), PendingIntent.FLAG_UPDATE_CURRENT
|
getPlayerActivityIntent(this), PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
or (if (Build.VERSION.SDK_INT >= 31) PendingIntent.FLAG_MUTABLE else 0)))
|
or (if (Build.VERSION.SDK_INT >= 31) PendingIntent.FLAG_MUTABLE else 0)))
|
||||||
|
@ -1278,17 +1300,17 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
if (playable == null || mediaPlayer == null) {
|
if (playable == null || mediaPlayer == null) {
|
||||||
Log.d(TAG, "setupNotification: playable=$playable")
|
Log.d(TAG, "setupNotification: playable=$playable")
|
||||||
Log.d(TAG, "setupNotification: mediaPlayer=$mediaPlayer")
|
Log.d(TAG, "setupNotification: mediaPlayer=$mediaPlayer")
|
||||||
if (!stateManager!!.hasReceivedValidStartCommand()) {
|
if (!stateManager.hasReceivedValidStartCommand()) {
|
||||||
stateManager!!.stopService()
|
stateManager.stopService()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val playerStatus = mediaPlayer!!.playerStatus
|
val playerStatus = mediaPlayer!!.playerStatus
|
||||||
notificationBuilder!!.setPlayable(playable)
|
notificationBuilder.setPlayable(playable)
|
||||||
notificationBuilder!!.setMediaSessionToken(mediaSession!!.sessionToken)
|
notificationBuilder.setMediaSessionToken(mediaSession!!.sessionToken)
|
||||||
notificationBuilder!!.playerStatus = playerStatus
|
notificationBuilder.playerStatus = playerStatus
|
||||||
notificationBuilder!!.updatePosition(currentPosition, currentPlaybackSpeed)
|
notificationBuilder.updatePosition(currentPosition, currentPlaybackSpeed)
|
||||||
|
|
||||||
val notificationManager = NotificationManagerCompat.from(this)
|
val notificationManager = NotificationManagerCompat.from(this)
|
||||||
if (ActivityCompat.checkSelfPermission(this,
|
if (ActivityCompat.checkSelfPermission(this,
|
||||||
|
@ -1302,14 +1324,14 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
// for ActivityCompat#requestPermissions for more details.
|
// for ActivityCompat#requestPermissions for more details.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
notificationManager.notify(R.id.notification_playing, notificationBuilder!!.build())
|
notificationManager.notify(R.id.notification_playing, notificationBuilder.build())
|
||||||
|
|
||||||
if (!notificationBuilder!!.isIconCached) {
|
if (!notificationBuilder.isIconCached) {
|
||||||
playableIconLoaderThread = Thread {
|
playableIconLoaderThread = Thread {
|
||||||
Log.d(TAG, "Loading notification icon")
|
Log.d(TAG, "Loading notification icon")
|
||||||
notificationBuilder!!.loadIcon()
|
notificationBuilder.loadIcon()
|
||||||
if (!Thread.currentThread().isInterrupted) {
|
if (!Thread.currentThread().isInterrupted) {
|
||||||
notificationManager.notify(R.id.notification_playing, notificationBuilder!!.build())
|
notificationManager.notify(R.id.notification_playing, notificationBuilder.build())
|
||||||
updateMediaSessionMetadata(playable)
|
updateMediaSessionMetadata(playable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1345,11 +1367,11 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sleepTimerActive(): Boolean {
|
fun sleepTimerActive(): Boolean {
|
||||||
return taskManager!!.isSleepTimerActive
|
return taskManager.isSleepTimerActive
|
||||||
}
|
}
|
||||||
|
|
||||||
val sleepTimerTimeLeft: Long
|
val sleepTimerTimeLeft: Long
|
||||||
get() = taskManager!!.sleepTimerTimeLeft
|
get() = taskManager.sleepTimerTimeLeft
|
||||||
|
|
||||||
private fun bluetoothNotifyChange(info: PSMPInfo?, whatChanged: String) {
|
private fun bluetoothNotifyChange(info: PSMPInfo?, whatChanged: String) {
|
||||||
var isPlaying = false
|
var isPlaying = false
|
||||||
|
@ -1379,17 +1401,17 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
if (!isConnectedToCar) {
|
if (!isConnectedToCar) {
|
||||||
Log.d(TAG, "Car was unplugged during playback.")
|
Log.d(TAG, "Car was unplugged during playback.")
|
||||||
} else {
|
} else {
|
||||||
val playerStatus = mediaPlayer!!.playerStatus
|
val playerStatus = mediaPlayer?.playerStatus
|
||||||
when (playerStatus) {
|
when (playerStatus) {
|
||||||
PlayerStatus.PAUSED, PlayerStatus.PREPARED -> {
|
PlayerStatus.PAUSED, PlayerStatus.PREPARED -> {
|
||||||
mediaPlayer!!.resume()
|
mediaPlayer?.resume()
|
||||||
}
|
}
|
||||||
PlayerStatus.PREPARING -> {
|
PlayerStatus.PREPARING -> {
|
||||||
mediaPlayer!!.setStartWhenPrepared(!mediaPlayer!!.isStartWhenPrepared())
|
mediaPlayer?.setStartWhenPrepared(!mediaPlayer!!.isStartWhenPrepared())
|
||||||
}
|
}
|
||||||
PlayerStatus.INITIALIZED -> {
|
PlayerStatus.INITIALIZED -> {
|
||||||
mediaPlayer!!.setStartWhenPrepared(true)
|
mediaPlayer?.setStartWhenPrepared(true)
|
||||||
mediaPlayer!!.prepare()
|
mediaPlayer?.prepare()
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
|
@ -1455,7 +1477,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
*/
|
*/
|
||||||
private fun pauseIfPauseOnDisconnect() {
|
private fun pauseIfPauseOnDisconnect() {
|
||||||
Log.d(TAG, "pauseIfPauseOnDisconnect()")
|
Log.d(TAG, "pauseIfPauseOnDisconnect()")
|
||||||
transientPause = (mediaPlayer!!.playerStatus == PlayerStatus.PLAYING)
|
transientPause = (mediaPlayer?.playerStatus == PlayerStatus.PLAYING)
|
||||||
if (isPauseOnHeadsetDisconnect && !isCasting) {
|
if (isPauseOnHeadsetDisconnect && !isCasting) {
|
||||||
mediaPlayer!!.pause(!isPersistNotify, false)
|
mediaPlayer!!.pause(!isPersistNotify, false)
|
||||||
}
|
}
|
||||||
|
@ -1465,23 +1487,23 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
* @param bluetooth true if the event for unpausing came from bluetooth
|
* @param bluetooth true if the event for unpausing came from bluetooth
|
||||||
*/
|
*/
|
||||||
private fun unpauseIfPauseOnDisconnect(bluetooth: Boolean) {
|
private fun unpauseIfPauseOnDisconnect(bluetooth: Boolean) {
|
||||||
if (mediaPlayer!!.isAudioChannelInUse) {
|
if (mediaPlayer != null && mediaPlayer!!.isAudioChannelInUse) {
|
||||||
Log.d(TAG, "unpauseIfPauseOnDisconnect() audio is in use")
|
Log.d(TAG, "unpauseIfPauseOnDisconnect() audio is in use")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (transientPause) {
|
if (transientPause) {
|
||||||
transientPause = false
|
transientPause = false
|
||||||
if (Build.VERSION.SDK_INT >= 31) {
|
if (Build.VERSION.SDK_INT >= 31) {
|
||||||
stateManager!!.stopService()
|
stateManager.stopService()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!bluetooth && isUnpauseOnHeadsetReconnect) {
|
if (!bluetooth && isUnpauseOnHeadsetReconnect) {
|
||||||
mediaPlayer!!.resume()
|
mediaPlayer?.resume()
|
||||||
} else if (bluetooth && isUnpauseOnBluetoothReconnect) {
|
} else if (bluetooth && isUnpauseOnBluetoothReconnect) {
|
||||||
// let the user know we've started playback again...
|
// let the user know we've started playback again...
|
||||||
val v = applicationContext.getSystemService(VIBRATOR_SERVICE) as? Vibrator
|
val v = applicationContext.getSystemService(VIBRATOR_SERVICE) as? Vibrator
|
||||||
v?.vibrate(500)
|
v?.vibrate(500)
|
||||||
mediaPlayer!!.resume()
|
mediaPlayer?.resume()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1490,7 +1512,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
if (TextUtils.equals(intent.action, PlaybackServiceInterface.ACTION_SHUTDOWN_PLAYBACK_SERVICE)) {
|
if (TextUtils.equals(intent.action, PlaybackServiceInterface.ACTION_SHUTDOWN_PLAYBACK_SERVICE)) {
|
||||||
EventBus.getDefault().post(PlaybackServiceEvent(PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN))
|
EventBus.getDefault().post(PlaybackServiceEvent(PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN))
|
||||||
stateManager!!.stopService()
|
stateManager.stopService()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1499,7 +1521,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun volumeAdaptionChanged(event: VolumeAdaptionChangedEvent) {
|
fun volumeAdaptionChanged(event: VolumeAdaptionChangedEvent) {
|
||||||
val playbackVolumeUpdater = PlaybackVolumeUpdater()
|
val playbackVolumeUpdater = PlaybackVolumeUpdater()
|
||||||
playbackVolumeUpdater.updateVolumeIfNecessary(mediaPlayer!!, event.feedId, event.volumeAdaptionSetting)
|
if (mediaPlayer != null) playbackVolumeUpdater.updateVolumeIfNecessary(mediaPlayer!!, event.feedId, event.volumeAdaptionSetting)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
|
@ -1508,7 +1530,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
if (playable is FeedMedia) {
|
if (playable is FeedMedia) {
|
||||||
if ((playable as FeedMedia).getItem()?.feed?.id == event.feedId) {
|
if ((playable as FeedMedia).getItem()?.feed?.id == event.feedId) {
|
||||||
if (event.speed == FeedPreferences.SPEED_USE_GLOBAL) {
|
if (event.speed == FeedPreferences.SPEED_USE_GLOBAL) {
|
||||||
playable?.let {setSpeed(getPlaybackSpeed(playable!!.getMediaType()))}
|
setSpeed(getPlaybackSpeed(playable!!.getMediaType()))
|
||||||
} else {
|
} else {
|
||||||
setSpeed(event.speed)
|
setSpeed(event.speed)
|
||||||
}
|
}
|
||||||
|
@ -1533,17 +1555,17 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resume() {
|
fun resume() {
|
||||||
mediaPlayer!!.resume()
|
mediaPlayer?.resume()
|
||||||
taskManager!!.restartSleepTimer()
|
taskManager.restartSleepTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun prepare() {
|
fun prepare() {
|
||||||
mediaPlayer!!.prepare()
|
mediaPlayer?.prepare()
|
||||||
taskManager!!.restartSleepTimer()
|
taskManager.restartSleepTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pause(abandonAudioFocus: Boolean, reinit: Boolean) {
|
fun pause(abandonAudioFocus: Boolean, reinit: Boolean) {
|
||||||
mediaPlayer!!.pause(abandonAudioFocus, reinit)
|
mediaPlayer?.pause(abandonAudioFocus, reinit)
|
||||||
}
|
}
|
||||||
|
|
||||||
val pSMPInfo: PSMPInfo
|
val pSMPInfo: PSMPInfo
|
||||||
|
@ -1553,7 +1575,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
get() = mediaPlayer!!.playerStatus
|
get() = mediaPlayer!!.playerStatus
|
||||||
|
|
||||||
val playable: Playable?
|
val playable: Playable?
|
||||||
get() = mediaPlayer!!.getPlayable()
|
get() = mediaPlayer?.getPlayable()
|
||||||
|
|
||||||
fun setSpeed(speed: Float) {
|
fun setSpeed(speed: Float) {
|
||||||
currentlyPlayingTemporaryPlaybackSpeed = speed
|
currentlyPlayingTemporaryPlaybackSpeed = speed
|
||||||
|
@ -1563,34 +1585,31 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
setPlaybackSpeed(speed)
|
setPlaybackSpeed(speed)
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaPlayer!!.setPlaybackParams(speed, isSkipSilence)
|
mediaPlayer?.setPlaybackParams(speed, isSkipSilence)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun skipSilence(skipSilence: Boolean) {
|
fun skipSilence(skipSilence: Boolean) {
|
||||||
mediaPlayer!!.setPlaybackParams(currentPlaybackSpeed, skipSilence)
|
mediaPlayer?.setPlaybackParams(currentPlaybackSpeed, skipSilence)
|
||||||
}
|
}
|
||||||
|
|
||||||
val currentPlaybackSpeed: Float
|
val currentPlaybackSpeed: Float
|
||||||
get() {
|
get() {
|
||||||
if (mediaPlayer == null) {
|
return mediaPlayer?.getPlaybackSpeed() ?: 1.0f
|
||||||
return 1.0f
|
|
||||||
}
|
|
||||||
return mediaPlayer!!.getPlaybackSpeed()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var isStartWhenPrepared: Boolean
|
var isStartWhenPrepared: Boolean
|
||||||
get() = mediaPlayer!!.isStartWhenPrepared()
|
get() = mediaPlayer?.isStartWhenPrepared() ?: false
|
||||||
set(s) {
|
set(s) {
|
||||||
mediaPlayer!!.setStartWhenPrepared(s)
|
if (mediaPlayer != null) mediaPlayer!!.setStartWhenPrepared(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun seekTo(t: Int) {
|
fun seekTo(t: Int) {
|
||||||
mediaPlayer!!.seekTo(t)
|
mediaPlayer?.seekTo(t)
|
||||||
EventBus.getDefault().post(PlaybackPositionEvent(t, duration))
|
EventBus.getDefault().post(PlaybackPositionEvent(t, duration))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun seekDelta(d: Int) {
|
private fun seekDelta(d: Int) {
|
||||||
mediaPlayer!!.seekDelta(d)
|
mediaPlayer?.seekDelta(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
val duration: Int
|
val duration: Int
|
||||||
|
@ -1599,10 +1618,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
* an invalid state.
|
* an invalid state.
|
||||||
*/
|
*/
|
||||||
get() {
|
get() {
|
||||||
if (mediaPlayer == null) {
|
return mediaPlayer?.getDuration() ?: Playable.INVALID_TIME
|
||||||
return Playable.INVALID_TIME
|
|
||||||
}
|
|
||||||
return mediaPlayer!!.getDuration()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val currentPosition: Int
|
val currentPosition: Int
|
||||||
|
@ -1611,63 +1627,48 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
* is in an invalid state.
|
* is in an invalid state.
|
||||||
*/
|
*/
|
||||||
get() {
|
get() {
|
||||||
if (mediaPlayer == null) {
|
return mediaPlayer?.getPosition() ?: Playable.INVALID_TIME
|
||||||
return Playable.INVALID_TIME
|
|
||||||
}
|
|
||||||
return mediaPlayer!!.getPosition()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val audioTracks: List<String?>?
|
val audioTracks: List<String?>
|
||||||
get() {
|
get() {
|
||||||
if (mediaPlayer == null) {
|
return mediaPlayer?.getAudioTracks() ?: listOf()
|
||||||
return emptyList<String>()
|
|
||||||
}
|
|
||||||
return mediaPlayer!!.getAudioTracks()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val selectedAudioTrack: Int
|
val selectedAudioTrack: Int
|
||||||
get() {
|
get() {
|
||||||
if (mediaPlayer == null) {
|
return mediaPlayer?.getSelectedAudioTrack() ?: -1
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return mediaPlayer!!.getSelectedAudioTrack()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setAudioTrack(track: Int) {
|
fun setAudioTrack(track: Int) {
|
||||||
if (mediaPlayer != null) {
|
mediaPlayer?.setAudioTrack(track)
|
||||||
mediaPlayer!!.setAudioTrack(track)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val isStreaming: Boolean
|
val isStreaming: Boolean
|
||||||
get() = mediaPlayer!!.isStreaming()
|
get() = mediaPlayer?.isStreaming() ?: false
|
||||||
|
|
||||||
val videoSize: Pair<Int, Int>?
|
val videoSize: Pair<Int, Int>?
|
||||||
get() = mediaPlayer!!.getVideoSize()
|
get() = mediaPlayer!!.getVideoSize()
|
||||||
|
|
||||||
private fun setupPositionObserver() {
|
private fun setupPositionObserver() {
|
||||||
if (positionEventTimer != null) {
|
positionEventTimer?.dispose()
|
||||||
positionEventTimer!!.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(TAG, "Setting up position observer")
|
Log.d(TAG, "Setting up position observer")
|
||||||
positionEventTimer = Observable.interval(1, TimeUnit.SECONDS)
|
positionEventTimer = Observable.interval(POSITION_EVENT_INTERVAL, TimeUnit.SECONDS)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe { number: Long? ->
|
.subscribe { number: Long? ->
|
||||||
EventBus.getDefault().post(PlaybackPositionEvent(currentPosition, duration))
|
EventBus.getDefault().post(PlaybackPositionEvent(currentPosition, duration))
|
||||||
if (Build.VERSION.SDK_INT < 29) {
|
if (Build.VERSION.SDK_INT < 29) {
|
||||||
notificationBuilder!!.updatePosition(currentPosition, currentPlaybackSpeed)
|
notificationBuilder.updatePosition(currentPosition, currentPlaybackSpeed)
|
||||||
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as? NotificationManager
|
||||||
notificationManager.notify(R.id.notification_playing, notificationBuilder!!.build())
|
notificationManager?.notify(R.id.notification_playing, notificationBuilder.build())
|
||||||
}
|
}
|
||||||
skipEndingIfNecessary()
|
skipEndingIfNecessary()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cancelPositionObserver() {
|
private fun cancelPositionObserver() {
|
||||||
if (positionEventTimer != null) {
|
positionEventTimer?.dispose()
|
||||||
positionEventTimer!!.dispose()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addPlayableToQueue(playable: Playable?) {
|
private fun addPlayableToQueue(playable: Playable?) {
|
||||||
|
@ -1727,7 +1728,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
|
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
Log.d(TAG, "onStop()")
|
Log.d(TAG, "onStop()")
|
||||||
mediaPlayer!!.stopPlayback(true)
|
mediaPlayer?.stopPlayback(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSkipToPrevious() {
|
override fun onSkipToPrevious() {
|
||||||
|
@ -1741,23 +1742,22 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onNextChapter() {
|
fun onNextChapter() {
|
||||||
val chapters = mediaPlayer!!.getPlayable()!!.getChapters()
|
val chapters = mediaPlayer?.getPlayable()?.getChapters() ?: listOf()
|
||||||
if (chapters.isEmpty()) {
|
if (chapters.isEmpty()) {
|
||||||
// No chapters, just fallback to next episode
|
// No chapters, just fallback to next episode
|
||||||
mediaPlayer!!.skip()
|
mediaPlayer?.skip()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val nextChapter = getCurrentChapterIndex(
|
val nextChapter = getCurrentChapterIndex(mediaPlayer?.getPlayable(), (mediaPlayer?.getPosition()?:0)) + 1
|
||||||
mediaPlayer!!.getPlayable(), mediaPlayer!!.getPosition()) + 1
|
|
||||||
|
|
||||||
if (chapters.size < nextChapter + 1) {
|
if (chapters.size < nextChapter + 1) {
|
||||||
// We are on the last chapter, just fallback to the next episode
|
// We are on the last chapter, just fallback to the next episode
|
||||||
mediaPlayer!!.skip()
|
mediaPlayer?.skip()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaPlayer!!.seekTo(chapters[nextChapter].start.toInt())
|
mediaPlayer?.seekTo(chapters[nextChapter].start.toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFastForward() {
|
override fun onFastForward() {
|
||||||
|
@ -1771,7 +1771,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
.getSystemService(UI_MODE_SERVICE) as UiModeManager
|
.getSystemService(UI_MODE_SERVICE) as UiModeManager
|
||||||
if (hardwareForwardButton == KeyEvent.KEYCODE_MEDIA_NEXT
|
if (hardwareForwardButton == KeyEvent.KEYCODE_MEDIA_NEXT
|
||||||
|| uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_CAR) {
|
|| uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_CAR) {
|
||||||
mediaPlayer!!.skip()
|
mediaPlayer?.skip()
|
||||||
} else {
|
} else {
|
||||||
seekDelta(fastForwardSecs * 1000)
|
seekDelta(fastForwardSecs * 1000)
|
||||||
}
|
}
|
||||||
|
@ -1820,30 +1820,36 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
|
|
||||||
override fun onCustomAction(action: String, extra: Bundle) {
|
override fun onCustomAction(action: String, extra: Bundle) {
|
||||||
Log.d(TAG, "onCustomAction($action)")
|
Log.d(TAG, "onCustomAction($action)")
|
||||||
if (CUSTOM_ACTION_FAST_FORWARD == action) {
|
when (action) {
|
||||||
onFastForward()
|
CUSTOM_ACTION_FAST_FORWARD -> {
|
||||||
} else if (CUSTOM_ACTION_REWIND == action) {
|
onFastForward()
|
||||||
onRewind()
|
}
|
||||||
} else if (CUSTOM_ACTION_SKIP_TO_NEXT == action) {
|
CUSTOM_ACTION_REWIND -> {
|
||||||
mediaPlayer!!.skip()
|
onRewind()
|
||||||
} else if (CUSTOM_ACTION_NEXT_CHAPTER == action) {
|
}
|
||||||
onNextChapter()
|
CUSTOM_ACTION_SKIP_TO_NEXT -> {
|
||||||
} else if (CUSTOM_ACTION_CHANGE_PLAYBACK_SPEED == action) {
|
mediaPlayer?.skip()
|
||||||
val selectedSpeeds = playbackSpeedArray
|
}
|
||||||
|
CUSTOM_ACTION_NEXT_CHAPTER -> {
|
||||||
|
onNextChapter()
|
||||||
|
}
|
||||||
|
CUSTOM_ACTION_CHANGE_PLAYBACK_SPEED -> {
|
||||||
|
val selectedSpeeds = playbackSpeedArray
|
||||||
|
|
||||||
// If the list has zero or one element, there's nothing we can do to change the playback speed.
|
// If the list has zero or one element, there's nothing we can do to change the playback speed.
|
||||||
if (selectedSpeeds.size > 1) {
|
if (selectedSpeeds.size > 1) {
|
||||||
val speedPosition = selectedSpeeds.indexOf(mediaPlayer!!.getPlaybackSpeed())
|
val speedPosition = selectedSpeeds.indexOf(mediaPlayer?.getPlaybackSpeed()?:0f)
|
||||||
|
|
||||||
val newSpeed = if (speedPosition == selectedSpeeds.size - 1) {
|
val newSpeed = if (speedPosition == selectedSpeeds.size - 1) {
|
||||||
// This is the last element. Wrap instead of going over the size of the list.
|
// This is the last element. Wrap instead of going over the size of the list.
|
||||||
selectedSpeeds[0]
|
selectedSpeeds[0]
|
||||||
} else {
|
} else {
|
||||||
// If speedPosition is still -1 (the user isn't using a preset), use the first preset in the
|
// If speedPosition is still -1 (the user isn't using a preset), use the first preset in the
|
||||||
// list.
|
// list.
|
||||||
selectedSpeeds[speedPosition + 1]
|
selectedSpeeds[speedPosition + 1]
|
||||||
|
}
|
||||||
|
onSetPlaybackSpeed(newSpeed)
|
||||||
}
|
}
|
||||||
onSetPlaybackSpeed(newSpeed)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1855,6 +1861,8 @@ class PlaybackService : MediaBrowserServiceCompat() {
|
||||||
*/
|
*/
|
||||||
private const val TAG = "PlaybackService"
|
private const val TAG = "PlaybackService"
|
||||||
|
|
||||||
|
private const val POSITION_EVENT_INTERVAL = 10L
|
||||||
|
|
||||||
const val ACTION_PLAYER_STATUS_CHANGED: String = "action.ac.mdiq.podvinci.core.service.playerStatusChanged"
|
const val ACTION_PLAYER_STATUS_CHANGED: String = "action.ac.mdiq.podvinci.core.service.playerStatusChanged"
|
||||||
private const val AVRCP_ACTION_PLAYER_STATUS_CHANGED = "com.android.music.playstatechanged"
|
private const val AVRCP_ACTION_PLAYER_STATUS_CHANGED = "com.android.music.playstatechanged"
|
||||||
private const val AVRCP_ACTION_META_CHANGED = "com.android.music.metachanged"
|
private const val AVRCP_ACTION_META_CHANGED = "com.android.music.metachanged"
|
||||||
|
|
|
@ -37,12 +37,10 @@ open class AutomaticDownloadAlgorithm {
|
||||||
@UnstableApi open fun autoDownloadUndownloadedItems(context: Context?): Runnable? {
|
@UnstableApi open fun autoDownloadUndownloadedItems(context: Context?): Runnable? {
|
||||||
return Runnable {
|
return Runnable {
|
||||||
// true if we should auto download based on network status
|
// true if we should auto download based on network status
|
||||||
val networkShouldAutoDl = (isAutoDownloadAllowed
|
val networkShouldAutoDl = (isAutoDownloadAllowed && isEnableAutodownload)
|
||||||
&& isEnableAutodownload)
|
|
||||||
|
|
||||||
// true if we should auto download based on power status
|
// true if we should auto download based on power status
|
||||||
val powerShouldAutoDl = (deviceCharging(context!!)
|
val powerShouldAutoDl = (deviceCharging(context!!) || isEnableAutodownloadOnBattery)
|
||||||
|| isEnableAutodownloadOnBattery)
|
|
||||||
|
|
||||||
// we should only auto download if both network AND power are happy
|
// we should only auto download if both network AND power are happy
|
||||||
if (networkShouldAutoDl && powerShouldAutoDl) {
|
if (networkShouldAutoDl && powerShouldAutoDl) {
|
||||||
|
@ -56,9 +54,7 @@ open class AutomaticDownloadAlgorithm {
|
||||||
candidates.addAll(queue)
|
candidates.addAll(queue)
|
||||||
for (newItem in newItems) {
|
for (newItem in newItems) {
|
||||||
val feedPrefs = newItem.feed!!.preferences
|
val feedPrefs = newItem.feed!!.preferences
|
||||||
if (feedPrefs!!.autoDownload
|
if (feedPrefs!!.autoDownload && !candidates.contains(newItem) && feedPrefs.filter.shouldAutoDownload(newItem)) {
|
||||||
&& !candidates.contains(newItem)
|
|
||||||
&& feedPrefs.filter.shouldAutoDownload(newItem)) {
|
|
||||||
candidates.add(newItem)
|
candidates.add(newItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,10 +74,8 @@ open class AutomaticDownloadAlgorithm {
|
||||||
|
|
||||||
val autoDownloadableEpisodes = candidates.size
|
val autoDownloadableEpisodes = candidates.size
|
||||||
val downloadedEpisodes = getTotalEpisodeCount(FeedItemFilter(FeedItemFilter.DOWNLOADED))
|
val downloadedEpisodes = getTotalEpisodeCount(FeedItemFilter(FeedItemFilter.DOWNLOADED))
|
||||||
val deletedEpisodes = build()
|
val deletedEpisodes = build().makeRoomForEpisodes(context, autoDownloadableEpisodes)
|
||||||
.makeRoomForEpisodes(context, autoDownloadableEpisodes)
|
val cacheIsUnlimited = episodeCacheSize == UserPreferences.EPISODE_CACHE_SIZE_UNLIMITED
|
||||||
val cacheIsUnlimited =
|
|
||||||
episodeCacheSize == UserPreferences.EPISODE_CACHE_SIZE_UNLIMITED
|
|
||||||
val episodeCacheSize = episodeCacheSize
|
val episodeCacheSize = episodeCacheSize
|
||||||
val episodeSpaceLeft =
|
val episodeSpaceLeft =
|
||||||
if (cacheIsUnlimited || episodeCacheSize >= downloadedEpisodes + autoDownloadableEpisodes) {
|
if (cacheIsUnlimited || episodeCacheSize >= downloadedEpisodes + autoDownloadableEpisodes) {
|
||||||
|
@ -99,6 +93,9 @@ open class AutomaticDownloadAlgorithm {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
Log.d(TAG, "not auto downloaded networkShouldAutoDl: $networkShouldAutoDl powerShouldAutoDl $powerShouldAutoDl")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -434,17 +434,17 @@ object DBReader {
|
||||||
*/
|
*/
|
||||||
fun getFeed(feedId: Long, filtered: Boolean): Feed? {
|
fun getFeed(feedId: Long, filtered: Boolean): Feed? {
|
||||||
Log.d(TAG, "getFeed() called with: feedId = [$feedId]")
|
Log.d(TAG, "getFeed() called with: feedId = [$feedId]")
|
||||||
val adapter = getInstance()
|
val adapter = getInstance() ?: return null
|
||||||
adapter!!.open()
|
adapter.open()
|
||||||
var feed: Feed? = null
|
|
||||||
try {
|
try {
|
||||||
adapter.getFeedCursor(feedId).use { cursor ->
|
adapter.getFeedCursor(feedId).use { cursor ->
|
||||||
|
var feed: Feed? = null
|
||||||
if (cursor.moveToNext()) {
|
if (cursor.moveToNext()) {
|
||||||
feed = extractFeedFromCursorRow(cursor)
|
feed = extractFeedFromCursorRow(cursor)
|
||||||
if (filtered) {
|
if (filtered) {
|
||||||
feed!!.items = getFeedItemList(feed, feed!!.itemFilter).toMutableList()
|
feed.items = getFeedItemList(feed, feed.itemFilter).toMutableList()
|
||||||
} else {
|
} else {
|
||||||
feed!!.items = getFeedItemList(feed).toMutableList()
|
feed.items = getFeedItemList(feed).toMutableList()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "getFeed could not find feed with id $feedId")
|
Log.e(TAG, "getFeed could not find feed with id $feedId")
|
||||||
|
|
|
@ -17,7 +17,7 @@ import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.nio.channels.FileChannel
|
import java.nio.channels.FileChannel
|
||||||
|
|
||||||
object DatabaseExporter {
|
object DatabaseTransporter {
|
||||||
private const val TAG = "DatabaseExporter"
|
private const val TAG = "DatabaseExporter"
|
||||||
private const val TEMP_DB_NAME = PodDBAdapter.DATABASE_NAME + "_tmp"
|
private const val TEMP_DB_NAME = PodDBAdapter.DATABASE_NAME + "_tmp"
|
||||||
|
|
|
@ -15,25 +15,21 @@ object FeedItemPermutors {
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getPermutor(sortOrder: SortOrder): Permutor<FeedItem> {
|
fun getPermutor(sortOrder: SortOrder): Permutor<FeedItem> {
|
||||||
var comparator: Comparator<FeedItem?>? = null
|
var comparator: Comparator<FeedItem>? = null
|
||||||
var permutor: Permutor<FeedItem>? = null
|
var permutor: Permutor<FeedItem>? = null
|
||||||
|
|
||||||
when (sortOrder) {
|
when (sortOrder) {
|
||||||
SortOrder.EPISODE_TITLE_A_Z -> comparator = Comparator { f1: FeedItem?, f2: FeedItem? ->
|
SortOrder.EPISODE_TITLE_A_Z -> comparator = Comparator { f1: FeedItem?, f2: FeedItem? ->
|
||||||
itemTitle(f1).compareTo(
|
itemTitle(f1).compareTo(itemTitle(f2))
|
||||||
itemTitle(f2))
|
|
||||||
}
|
}
|
||||||
SortOrder.EPISODE_TITLE_Z_A -> comparator = Comparator { f1: FeedItem?, f2: FeedItem? ->
|
SortOrder.EPISODE_TITLE_Z_A -> comparator = Comparator { f1: FeedItem?, f2: FeedItem? ->
|
||||||
itemTitle(f2).compareTo(
|
itemTitle(f2).compareTo(itemTitle(f1))
|
||||||
itemTitle(f1))
|
|
||||||
}
|
}
|
||||||
SortOrder.DATE_OLD_NEW -> comparator = Comparator { f1: FeedItem?, f2: FeedItem? ->
|
SortOrder.DATE_OLD_NEW -> comparator = Comparator { f1: FeedItem?, f2: FeedItem? ->
|
||||||
pubDate(f1).compareTo(
|
pubDate(f1).compareTo(pubDate(f2))
|
||||||
pubDate(f2))
|
|
||||||
}
|
}
|
||||||
SortOrder.DATE_NEW_OLD -> comparator = Comparator { f1: FeedItem?, f2: FeedItem? ->
|
SortOrder.DATE_NEW_OLD -> comparator = Comparator { f1: FeedItem?, f2: FeedItem? ->
|
||||||
pubDate(f2).compareTo(
|
pubDate(f2).compareTo(pubDate(f1))
|
||||||
pubDate(f1))
|
|
||||||
}
|
}
|
||||||
SortOrder.DURATION_SHORT_LONG -> comparator = Comparator { f1: FeedItem?, f2: FeedItem? ->
|
SortOrder.DURATION_SHORT_LONG -> comparator = Comparator { f1: FeedItem?, f2: FeedItem? ->
|
||||||
duration(f1).compareTo(duration(f2))
|
duration(f1).compareTo(duration(f2))
|
||||||
|
@ -42,20 +38,16 @@ object FeedItemPermutors {
|
||||||
duration(f2).compareTo(duration(f1))
|
duration(f2).compareTo(duration(f1))
|
||||||
}
|
}
|
||||||
SortOrder.EPISODE_FILENAME_A_Z -> comparator = Comparator { f1: FeedItem?, f2: FeedItem? ->
|
SortOrder.EPISODE_FILENAME_A_Z -> comparator = Comparator { f1: FeedItem?, f2: FeedItem? ->
|
||||||
itemLink(f1).compareTo(
|
itemLink(f1).compareTo(itemLink(f2))
|
||||||
itemLink(f2))
|
|
||||||
}
|
}
|
||||||
SortOrder.EPISODE_FILENAME_Z_A -> comparator = Comparator { f1: FeedItem?, f2: FeedItem? ->
|
SortOrder.EPISODE_FILENAME_Z_A -> comparator = Comparator { f1: FeedItem?, f2: FeedItem? ->
|
||||||
itemLink(f2).compareTo(
|
itemLink(f2).compareTo(itemLink(f1))
|
||||||
itemLink(f1))
|
|
||||||
}
|
}
|
||||||
SortOrder.FEED_TITLE_A_Z -> comparator = Comparator { f1: FeedItem?, f2: FeedItem? ->
|
SortOrder.FEED_TITLE_A_Z -> comparator = Comparator { f1: FeedItem?, f2: FeedItem? ->
|
||||||
feedTitle(f1).compareTo(
|
feedTitle(f1).compareTo(feedTitle(f2))
|
||||||
feedTitle(f2))
|
|
||||||
}
|
}
|
||||||
SortOrder.FEED_TITLE_Z_A -> comparator = Comparator { f1: FeedItem?, f2: FeedItem? ->
|
SortOrder.FEED_TITLE_Z_A -> comparator = Comparator { f1: FeedItem?, f2: FeedItem? ->
|
||||||
feedTitle(f2).compareTo(
|
feedTitle(f2).compareTo(feedTitle(f1))
|
||||||
feedTitle(f1))
|
|
||||||
}
|
}
|
||||||
SortOrder.RANDOM -> permutor = object : Permutor<FeedItem> {
|
SortOrder.RANDOM -> permutor = object : Permutor<FeedItem> {
|
||||||
override fun reorder(queue: MutableList<FeedItem>?) {if (!queue.isNullOrEmpty()) queue.shuffle()}
|
override fun reorder(queue: MutableList<FeedItem>?) {if (!queue.isNullOrEmpty()) queue.shuffle()}
|
||||||
|
@ -74,9 +66,9 @@ object FeedItemPermutors {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (comparator != null) {
|
if (comparator != null) {
|
||||||
val comparator2: Comparator<FeedItem?> = comparator
|
val comparator2: Comparator<FeedItem> = comparator
|
||||||
permutor = object : Permutor<FeedItem> {
|
permutor = object : Permutor<FeedItem> {
|
||||||
override fun reorder(queue: MutableList<FeedItem>?) {if (!queue.isNullOrEmpty()) queue.sortedWith(comparator2)}
|
override fun reorder(queue: MutableList<FeedItem>?) {if (!queue.isNullOrEmpty()) queue.sortWith(comparator2)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return permutor!!
|
return permutor!!
|
||||||
|
@ -84,32 +76,27 @@ object FeedItemPermutors {
|
||||||
|
|
||||||
// Null-safe accessors
|
// Null-safe accessors
|
||||||
private fun pubDate(item: FeedItem?): Date {
|
private fun pubDate(item: FeedItem?): Date {
|
||||||
return if ((item != null && item.pubDate != null)) item.pubDate!!
|
return if (item?.pubDate != null) item.pubDate!! else Date(0)
|
||||||
else Date(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun itemTitle(item: FeedItem?): String {
|
private fun itemTitle(item: FeedItem?): String {
|
||||||
return if ((item != null && item.title != null)) item.title!!.lowercase(Locale.getDefault())
|
return if (item?.title != null) item.title!!.lowercase(Locale.getDefault()) else ""
|
||||||
else ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun duration(item: FeedItem?): Int {
|
private fun duration(item: FeedItem?): Int {
|
||||||
return if ((item != null && item.media != null)) item.media!!.getDuration()
|
return if (item?.media != null) item.media!!.getDuration() else 0
|
||||||
else 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun size(item: FeedItem?): Long {
|
private fun size(item: FeedItem?): Long {
|
||||||
return if ((item != null && item.media != null)) item.media!!.size else 0
|
return if (item?.media != null) item.media!!.size else 0
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun itemLink(item: FeedItem?): String {
|
private fun itemLink(item: FeedItem?): String {
|
||||||
return if ((item != null && item.link != null)
|
return if (item?.link != null) item.link!!.lowercase(Locale.getDefault()) else ""
|
||||||
) item.link!!.lowercase(Locale.getDefault()) else ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun feedTitle(item: FeedItem?): String {
|
private fun feedTitle(item: FeedItem?): String {
|
||||||
return if ((item != null && item.feed != null && item.feed!!.title != null)
|
return if (item?.feed != null && item.feed!!.title != null) item.feed!!.title!!.lowercase(Locale.getDefault()) else ""
|
||||||
) item.feed!!.title!!.lowercase(Locale.getDefault()) else ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -147,8 +134,8 @@ object FeedItemPermutors {
|
||||||
|
|
||||||
// Sort each individual list by PubDate (ascending/descending)
|
// Sort each individual list by PubDate (ascending/descending)
|
||||||
val itemComparator: Comparator<FeedItem> = if (ascending)
|
val itemComparator: Comparator<FeedItem> = if (ascending)
|
||||||
Comparator { f1: FeedItem, f2: FeedItem -> f1.pubDate?.compareTo(f2!!.pubDate)?:-1 }
|
Comparator { f1: FeedItem, f2: FeedItem -> f1.pubDate?.compareTo(f2.pubDate)?:-1 }
|
||||||
else Comparator { f1: FeedItem, f2: FeedItem -> f2.pubDate?.compareTo(f1!!.pubDate)?:-1 }
|
else Comparator { f1: FeedItem, f2: FeedItem -> f2.pubDate?.compareTo(f1.pubDate)?:-1 }
|
||||||
|
|
||||||
val feeds: MutableList<List<FeedItem>> = ArrayList()
|
val feeds: MutableList<List<FeedItem>> = ArrayList()
|
||||||
for ((_, value) in map) {
|
for ((_, value) in map) {
|
||||||
|
|
|
@ -1,12 +1,5 @@
|
||||||
package ac.mdiq.podvinci.core.util.playback
|
package ac.mdiq.podvinci.core.util.playback
|
||||||
|
|
||||||
import android.content.*
|
|
||||||
import android.os.IBinder
|
|
||||||
import android.util.Log
|
|
||||||
import android.util.Pair
|
|
||||||
import android.view.SurfaceHolder
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import androidx.media3.common.util.UnstableApi
|
|
||||||
import ac.mdiq.podvinci.core.feed.util.PlaybackSpeedUtils.getCurrentPlaybackSpeed
|
import ac.mdiq.podvinci.core.feed.util.PlaybackSpeedUtils.getCurrentPlaybackSpeed
|
||||||
import ac.mdiq.podvinci.core.preferences.PlaybackPreferences
|
import ac.mdiq.podvinci.core.preferences.PlaybackPreferences
|
||||||
import ac.mdiq.podvinci.core.service.playback.PlaybackService
|
import ac.mdiq.podvinci.core.service.playback.PlaybackService
|
||||||
|
@ -20,6 +13,15 @@ import ac.mdiq.podvinci.model.feed.FeedMedia
|
||||||
import ac.mdiq.podvinci.model.playback.MediaType
|
import ac.mdiq.podvinci.model.playback.MediaType
|
||||||
import ac.mdiq.podvinci.model.playback.Playable
|
import ac.mdiq.podvinci.model.playback.Playable
|
||||||
import ac.mdiq.podvinci.playback.base.PlayerStatus
|
import ac.mdiq.podvinci.playback.base.PlayerStatus
|
||||||
|
import android.content.*
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.util.Log
|
||||||
|
import android.util.Pair
|
||||||
|
import android.view.SurfaceHolder
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
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
|
||||||
|
@ -71,10 +73,20 @@ abstract class PlaybackController(private val activity: FragmentActivity?) {
|
||||||
}
|
}
|
||||||
initialized = true
|
initialized = true
|
||||||
|
|
||||||
activity?.registerReceiver(statusUpdate, IntentFilter(
|
// TODO: this shit doesn't work
|
||||||
PlaybackService.ACTION_PLAYER_STATUS_CHANGED))
|
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
activity?.registerReceiver(notificationReceiver, IntentFilter(
|
// activity?.registerReceiver(statusUpdate, IntentFilter(
|
||||||
PlaybackServiceInterface.ACTION_PLAYER_NOTIFICATION))
|
// PlaybackService.ACTION_PLAYER_STATUS_CHANGED), Context.RECEIVER_NOT_EXPORTED)
|
||||||
|
// activity?.registerReceiver(notificationReceiver, IntentFilter(
|
||||||
|
// PlaybackServiceInterface.ACTION_PLAYER_NOTIFICATION), Context.RECEIVER_NOT_EXPORTED)
|
||||||
|
// } else {
|
||||||
|
// ContextCompat.registerReceiver(activity!!, statusUpdate, IntentFilter(
|
||||||
|
// PlaybackService.ACTION_PLAYER_STATUS_CHANGED), ContextCompat.RECEIVER_EXPORTED)
|
||||||
|
// ContextCompat.registerReceiver(activity, notificationReceiver, IntentFilter(
|
||||||
|
// PlaybackServiceInterface.ACTION_PLAYER_NOTIFICATION), ContextCompat.RECEIVER_EXPORTED)
|
||||||
|
// }
|
||||||
|
activity?.registerReceiver(statusUpdate, IntentFilter(PlaybackService.ACTION_PLAYER_STATUS_CHANGED))
|
||||||
|
activity?.registerReceiver(notificationReceiver, IntentFilter(PlaybackServiceInterface.ACTION_PLAYER_NOTIFICATION))
|
||||||
|
|
||||||
if (!released) {
|
if (!released) {
|
||||||
bindToService()
|
bindToService()
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package ac.mdiq.podvinci.parser.feed.namespace
|
package ac.mdiq.podvinci.parser.feed.namespace
|
||||||
|
|
||||||
import android.text.TextUtils
|
|
||||||
import android.util.Log
|
|
||||||
import ac.mdiq.podvinci.model.feed.FeedMedia
|
import ac.mdiq.podvinci.model.feed.FeedMedia
|
||||||
import ac.mdiq.podvinci.parser.feed.HandlerState
|
import ac.mdiq.podvinci.parser.feed.HandlerState
|
||||||
import ac.mdiq.podvinci.parser.feed.element.AtomText
|
import ac.mdiq.podvinci.parser.feed.element.AtomText
|
||||||
|
@ -9,13 +7,13 @@ import ac.mdiq.podvinci.parser.feed.element.SyndElement
|
||||||
import ac.mdiq.podvinci.parser.feed.util.MimeTypeUtils.getMimeType
|
import ac.mdiq.podvinci.parser.feed.util.MimeTypeUtils.getMimeType
|
||||||
import ac.mdiq.podvinci.parser.feed.util.MimeTypeUtils.isImageFile
|
import ac.mdiq.podvinci.parser.feed.util.MimeTypeUtils.isImageFile
|
||||||
import ac.mdiq.podvinci.parser.feed.util.MimeTypeUtils.isMediaFile
|
import ac.mdiq.podvinci.parser.feed.util.MimeTypeUtils.isMediaFile
|
||||||
|
import android.util.Log
|
||||||
import org.xml.sax.Attributes
|
import org.xml.sax.Attributes
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
/** Processes tags from the http://search.yahoo.com/mrss/ namespace. */
|
/** Processes tags from the http://search.yahoo.com/mrss/ namespace. */
|
||||||
class Media : Namespace() {
|
class Media : Namespace() {
|
||||||
override fun handleElementStart(localName: String, state: HandlerState,
|
override fun handleElementStart(localName: String, state: HandlerState, attributes: Attributes): SyndElement {
|
||||||
attributes: Attributes): SyndElement {
|
|
||||||
if (CONTENT == localName) {
|
if (CONTENT == localName) {
|
||||||
val url = attributes.getValue(DOWNLOAD_URL)
|
val url = attributes.getValue(DOWNLOAD_URL)
|
||||||
val defaultStr = attributes.getValue(DEFAULT)
|
val defaultStr = attributes.getValue(DEFAULT)
|
||||||
|
@ -47,15 +45,16 @@ class Media : Namespace() {
|
||||||
if (state.currentItem != null && (state.currentItem!!.media == null || isDefault) && url != null && validTypeMedia) {
|
if (state.currentItem != null && (state.currentItem!!.media == null || isDefault) && url != null && validTypeMedia) {
|
||||||
var size: Long = 0
|
var size: Long = 0
|
||||||
val sizeStr = attributes.getValue(SIZE)
|
val sizeStr = attributes.getValue(SIZE)
|
||||||
try {
|
if (!sizeStr.isNullOrEmpty()) {
|
||||||
size = sizeStr.toLong()
|
try {
|
||||||
} catch (e: NumberFormatException) {
|
size = sizeStr.toLong()
|
||||||
Log.e(TAG, "Size \"$sizeStr\" could not be parsed.")
|
} catch (e: NumberFormatException) {
|
||||||
|
Log.e(TAG, "Size \"$sizeStr\" could not be parsed.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var durationMs = 0
|
var durationMs = 0
|
||||||
val durationStr = attributes.getValue(DURATION)
|
val durationStr = attributes.getValue(DURATION)
|
||||||
if (!TextUtils.isEmpty(durationStr)) {
|
if (!durationStr.isNullOrEmpty()) {
|
||||||
try {
|
try {
|
||||||
val duration = durationStr.toLong()
|
val duration = durationStr.toLong()
|
||||||
durationMs = TimeUnit.MILLISECONDS.convert(duration, TimeUnit.SECONDS).toInt()
|
durationMs = TimeUnit.MILLISECONDS.convert(duration, TimeUnit.SECONDS).toInt()
|
||||||
|
|
Loading…
Reference in New Issue