diff --git a/.gitignore b/.gitignore index d4be5aef..7397a4f0 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ target/ build/ **/*.project **/*.classpath +**/.directory # Local configuration file (sdk path, etc) local.properties diff --git a/app/build.gradle b/app/build.gradle index 0eedd4d8..86943d07 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 { diff --git a/app/src/main/java/ac/mdiq/podvinci/adapter/EpisodeItemListAdapter.kt b/app/src/main/java/ac/mdiq/podvinci/adapter/EpisodeItemListAdapter.kt index a253058c..e20414ce 100644 --- a/app/src/main/java/ac/mdiq/podvinci/adapter/EpisodeItemListAdapter.kt +++ b/app/src/main/java/ac/mdiq/podvinci/adapter/EpisodeItemListAdapter.kt @@ -22,6 +22,7 @@ import java.lang.ref.WeakReference */ open class EpisodeItemListAdapter(mainActivity: MainActivity) : SelectableAdapter(mainActivity), View.OnCreateContextMenuListener { + private val mainActivityRef: WeakReference = WeakReference(mainActivity) private var episodes: List = 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 diff --git a/app/src/main/java/ac/mdiq/podvinci/adapter/SelectableAdapter.kt b/app/src/main/java/ac/mdiq/podvinci/adapter/SelectableAdapter.kt index 2ecb790f..113167df 100644 --- a/app/src/main/java/ac/mdiq/podvinci/adapter/SelectableAdapter.kt +++ b/app/src/main/java/ac/mdiq/podvinci/adapter/SelectableAdapter.kt @@ -12,6 +12,7 @@ import ac.mdiq.podvinci.R */ abstract class SelectableAdapter(private val activity: Activity) : RecyclerView.Adapter() { + private var actionMode: ActionMode? = null private val selectedIds = HashSet() private var onSelectModeListener: OnSelectModeListener? = null diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/FeedItemlistFragment.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/FeedItemlistFragment.kt index 8f9b6454..6b95cc88 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/FeedItemlistFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/FeedItemlistFragment.kt @@ -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 = feed.items - FeedItemPermutors.getPermutor(feed.sortOrder!!).reorder(feedItems.toMutableList()) + FeedItemPermutors.getPermutor(feed.sortOrder!!).reorder(feedItems) feed.items = feedItems } } diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/preferences/ImportExportPreferencesFragment.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/preferences/ImportExportPreferencesFragment.kt index f60c26a2..e51b3904 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/preferences/ImportExportPreferencesFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/preferences/ImportExportPreferencesFragment.kt @@ -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({ diff --git a/core/src/main/java/ac/mdiq/podvinci/core/service/playback/ExoPlayerWrapper.kt b/core/src/main/java/ac/mdiq/podvinci/core/service/playback/ExoPlayerWrapper.kt index 5f8de6fc..75c0f2bb 100644 --- a/core/src/main/java/ac/mdiq/podvinci/core/service/playback/ExoPlayerWrapper.kt +++ b/core/src/main/java/ac/mdiq/podvinci/core/service/playback/ExoPlayerWrapper.kt @@ -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? = null private var bufferingUpdateListener: Consumer? = 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 @@ -264,8 +261,8 @@ class ExoPlayerWrapper internal constructor(private val context: Context) { private val formats: List get() { - val formats: MutableList = ArrayList() - val trackInfo = trackSelector!!.currentMappedTrackInfo + val formats: MutableList = 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?) { diff --git a/core/src/main/java/ac/mdiq/podvinci/core/service/playback/PlaybackService.kt b/core/src/main/java/ac/mdiq/podvinci/core/service/playback/PlaybackService.kt index e72dce9c..545c4783 100644 --- a/core/src/main/java/ac/mdiq/podvinci/core/service/playback/PlaybackService.kt +++ b/core/src/main/java/ac/mdiq/podvinci/core/service/playback/PlaybackService.kt @@ -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(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? + val audioTracks: List get() { - if (mediaPlayer == null) { - return emptyList() - } - 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? 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" diff --git a/core/src/main/java/ac/mdiq/podvinci/core/storage/AutomaticDownloadAlgorithm.kt b/core/src/main/java/ac/mdiq/podvinci/core/storage/AutomaticDownloadAlgorithm.kt index ef3d084b..316d7e8e 100644 --- a/core/src/main/java/ac/mdiq/podvinci/core/storage/AutomaticDownloadAlgorithm.kt +++ b/core/src/main/java/ac/mdiq/podvinci/core/storage/AutomaticDownloadAlgorithm.kt @@ -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") + } } } diff --git a/core/src/main/java/ac/mdiq/podvinci/core/storage/DBReader.kt b/core/src/main/java/ac/mdiq/podvinci/core/storage/DBReader.kt index 083755e1..a0c5b53f 100644 --- a/core/src/main/java/ac/mdiq/podvinci/core/storage/DBReader.kt +++ b/core/src/main/java/ac/mdiq/podvinci/core/storage/DBReader.kt @@ -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") diff --git a/core/src/main/java/ac/mdiq/podvinci/core/storage/DatabaseExporter.kt b/core/src/main/java/ac/mdiq/podvinci/core/storage/DatabaseTransporter.kt similarity index 99% rename from core/src/main/java/ac/mdiq/podvinci/core/storage/DatabaseExporter.kt rename to core/src/main/java/ac/mdiq/podvinci/core/storage/DatabaseTransporter.kt index 28fe9a3c..ff73108a 100644 --- a/core/src/main/java/ac/mdiq/podvinci/core/storage/DatabaseExporter.kt +++ b/core/src/main/java/ac/mdiq/podvinci/core/storage/DatabaseTransporter.kt @@ -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" diff --git a/core/src/main/java/ac/mdiq/podvinci/core/util/FeedItemPermutors.kt b/core/src/main/java/ac/mdiq/podvinci/core/util/FeedItemPermutors.kt index e70a8088..72063e60 100644 --- a/core/src/main/java/ac/mdiq/podvinci/core/util/FeedItemPermutors.kt +++ b/core/src/main/java/ac/mdiq/podvinci/core/util/FeedItemPermutors.kt @@ -15,25 +15,21 @@ object FeedItemPermutors { */ @JvmStatic fun getPermutor(sortOrder: SortOrder): Permutor { - var comparator: Comparator? = null + var comparator: Comparator? = null var permutor: Permutor? = 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 { override fun reorder(queue: MutableList?) {if (!queue.isNullOrEmpty()) queue.shuffle()} @@ -74,9 +66,9 @@ object FeedItemPermutors { } } if (comparator != null) { - val comparator2: Comparator = comparator + val comparator2: Comparator = comparator permutor = object : Permutor { - override fun reorder(queue: MutableList?) {if (!queue.isNullOrEmpty()) queue.sortedWith(comparator2)} + override fun reorder(queue: MutableList?) {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 = 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> = ArrayList() for ((_, value) in map) { diff --git a/core/src/main/java/ac/mdiq/podvinci/core/util/playback/PlaybackController.kt b/core/src/main/java/ac/mdiq/podvinci/core/util/playback/PlaybackController.kt index 29dba4d9..f5d48b1a 100644 --- a/core/src/main/java/ac/mdiq/podvinci/core/util/playback/PlaybackController.kt +++ b/core/src/main/java/ac/mdiq/podvinci/core/util/playback/PlaybackController.kt @@ -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() diff --git a/parser/feed/src/main/java/ac/mdiq/podvinci/parser/feed/namespace/Media.kt b/parser/feed/src/main/java/ac/mdiq/podvinci/parser/feed/namespace/Media.kt index 245983f4..2a42747d 100644 --- a/parser/feed/src/main/java/ac/mdiq/podvinci/parser/feed/namespace/Media.kt +++ b/parser/feed/src/main/java/ac/mdiq/podvinci/parser/feed/namespace/Media.kt @@ -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()