tuning and bug fixes

This commit is contained in:
Xilin Jia 2024-02-15 15:10:16 +01:00
parent 9a96130fa7
commit 0e36e6f39e
14 changed files with 406 additions and 394 deletions

1
.gitignore vendored
View File

@ -15,6 +15,7 @@ target/
build/
**/*.project
**/*.classpath
**/.directory
# Local configuration file (sdk path, etc)
local.properties

View File

@ -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 {

View File

@ -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) {
startSelectMode(longPressedPosition)
return true
} else if (item.itemId == R.id.select_all_above) {
setSelected(0, longPressedPosition, true)
return true
} else if (item.itemId == R.id.select_all_below) {
shouldSelectLazyLoadedItems = true
setSelected(longPressedPosition + 1, itemCount, true)
return true
when (item.itemId) {
R.id.multi_select -> {
startSelectMode(longPressedPosition)
return true
}
R.id.select_all_above -> {
setSelected(0, longPressedPosition, 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>

View File

@ -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

View File

@ -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
}
}

View File

@ -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({

View File

@ -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()
.setAudioOffloadMode(AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_ENABLED) // Add additional options as needed
.setIsGaplessSupportRequired(true)
.build()
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>?) {

View File

@ -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 = null
}
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) {
startPlayingFromPreferences()
} else {
return false
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 -> {
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) {
startPlayingFromPreferences()
} else {
return false
when {
status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED -> {
mediaPlayer?.resume()
}
status == PlayerStatus.INITIALIZED -> {
mediaPlayer?.setStartWhenPrepared(true)
mediaPlayer?.prepare()
}
mediaPlayer?.getPlayable() == null -> {
startPlayingFromPreferences()
}
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,30 +1820,36 @@ class PlaybackService : MediaBrowserServiceCompat() {
override fun onCustomAction(action: String, extra: Bundle) {
Log.d(TAG, "onCustomAction($action)")
if (CUSTOM_ACTION_FAST_FORWARD == action) {
onFastForward()
} else if (CUSTOM_ACTION_REWIND == action) {
onRewind()
} else if (CUSTOM_ACTION_SKIP_TO_NEXT == action) {
mediaPlayer!!.skip()
} else if (CUSTOM_ACTION_NEXT_CHAPTER == action) {
onNextChapter()
} else if (CUSTOM_ACTION_CHANGE_PLAYBACK_SPEED == action) {
val selectedSpeeds = playbackSpeedArray
when (action) {
CUSTOM_ACTION_FAST_FORWARD -> {
onFastForward()
}
CUSTOM_ACTION_REWIND -> {
onRewind()
}
CUSTOM_ACTION_SKIP_TO_NEXT -> {
mediaPlayer?.skip()
}
CUSTOM_ACTION_NEXT_CHAPTER -> {
onNextChapter()
}
CUSTOM_ACTION_CHANGE_PLAYBACK_SPEED -> {
val selectedSpeeds = playbackSpeedArray
// If the list has zero or one element, there's nothing we can do to change the playback speed.
if (selectedSpeeds.size > 1) {
val speedPosition = selectedSpeeds.indexOf(mediaPlayer!!.getPlaybackSpeed())
// If the list has zero or one element, there's nothing we can do to change the playback speed.
if (selectedSpeeds.size > 1) {
val speedPosition = selectedSpeeds.indexOf(mediaPlayer?.getPlaybackSpeed()?:0f)
val newSpeed = if (speedPosition == selectedSpeeds.size - 1) {
// This is the last element. Wrap instead of going over the size of the list.
selectedSpeeds[0]
} else {
// If speedPosition is still -1 (the user isn't using a preset), use the first preset in the
// list.
selectedSpeeds[speedPosition + 1]
val newSpeed = if (speedPosition == selectedSpeeds.size - 1) {
// This is the last element. Wrap instead of going over the size of the list.
selectedSpeeds[0]
} else {
// If speedPosition is still -1 (the user isn't using a preset), use the first preset in the
// list.
selectedSpeeds[speedPosition + 1]
}
onSetPlaybackSpeed(newSpeed)
}
onSetPlaybackSpeed(newSpeed)
}
}
}
@ -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"

View File

@ -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")
}
}
}

View File

@ -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")

View File

@ -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"

View File

@ -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) {

View File

@ -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()

View File

@ -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)
try {
size = sizeStr.toLong()
} catch (e: NumberFormatException) {
Log.e(TAG, "Size \"$sizeStr\" could not be parsed.")
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()