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