diff --git a/app/src/main/java/org/y20k/transistor/Keys.kt b/app/src/main/java/org/y20k/transistor/Keys.kt index 49babd0e..498f8fde 100644 --- a/app/src/main/java/org/y20k/transistor/Keys.kt +++ b/app/src/main/java/org/y20k/transistor/Keys.kt @@ -31,8 +31,6 @@ object Keys { // time values const val UPDATE_REPEAT_INTERVAL: Long = 4L // every 4 hours const val MINIMUM_TIME_BETWEEN_UPDATES: Long = 180000L // 3 minutes in milliseconds - const val SKIP_BACK_TIME_SPAN: Long = 10000L // 10 seconds in milliseconds - const val SKIP_FORWARD_TIME_SPAN: Long = 30000L // 30 seconds in milliseconds const val SLEEP_TIMER_DURATION: Long = 900000L // 15 minutes in milliseconds const val SLEEP_TIMER_INTERVAL: Long = 1000L // 1 second in milliseconds @@ -72,6 +70,9 @@ object Keys { const val CMD_CANCEL_SLEEP_TIMER: String = "CANCEL_SLEEP_TIMER" const val CMD_PLAY_STREAM: String = "PLAY_STREAM" const val CMD_DISMISS_NOTIFICATION: String = "DISMISS_NOTIFICATION" + const val CMD_NEXT_STATION: String = "NEXT_STATION" + const val CMD_PREVIOUS_STATION: String = "PREVIOUS_STATION" + // preferences const val PREF_RADIO_BROWSER_API: String = "RADIO_BROWSER_API" diff --git a/app/src/main/java/org/y20k/transistor/core/Collection.kt b/app/src/main/java/org/y20k/transistor/core/Collection.kt index 20d59c60..acfe4e2d 100644 --- a/app/src/main/java/org/y20k/transistor/core/Collection.kt +++ b/app/src/main/java/org/y20k/transistor/core/Collection.kt @@ -16,7 +16,7 @@ package org.y20k.transistor.core import android.os.Parcelable import androidx.annotation.Keep import com.google.gson.annotations.Expose -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize import org.y20k.transistor.Keys import java.util.* diff --git a/app/src/main/java/org/y20k/transistor/core/Station.kt b/app/src/main/java/org/y20k/transistor/core/Station.kt index 9a79541d..0f35ce35 100644 --- a/app/src/main/java/org/y20k/transistor/core/Station.kt +++ b/app/src/main/java/org/y20k/transistor/core/Station.kt @@ -18,7 +18,7 @@ import android.os.Parcelable import android.support.v4.media.session.PlaybackStateCompat import androidx.annotation.Keep import com.google.gson.annotations.Expose -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize import org.y20k.transistor.Keys import java.util.* diff --git a/app/src/main/java/org/y20k/transistor/dialogs/SelectStreamDialog.kt b/app/src/main/java/org/y20k/transistor/dialogs/SelectStreamDialog.kt new file mode 100644 index 00000000..60b5cf6b --- /dev/null +++ b/app/src/main/java/org/y20k/transistor/dialogs/SelectStreamDialog.kt @@ -0,0 +1,25 @@ +package org.y20k.transistor.dialogs + +import androidx.appcompat.app.AlertDialog +import org.y20k.transistor.helpers.LogHelper + + +/* + * SelectStreamDialog class + */ +class SelectStreamDialog { + + /* Define log tag */ + private val TAG = LogHelper.makeLogTag(FindStationDialog::class.java.simpleName) + + + /* Main class variables */ + private lateinit var dialog: AlertDialog + + + /* Construct and show dialog */ + fun show() { + + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/y20k/transistor/helpers/CollectionHelper.kt b/app/src/main/java/org/y20k/transistor/helpers/CollectionHelper.kt index 38224741..cf301c07 100644 --- a/app/src/main/java/org/y20k/transistor/helpers/CollectionHelper.kt +++ b/app/src/main/java/org/y20k/transistor/helpers/CollectionHelper.kt @@ -459,7 +459,7 @@ object CollectionHelper { /* Creates description for a single episode - used in MediaSessionConnector */ - fun buildStationMediaDescription(context: Context, station: Station): MediaDescriptionCompat { + fun buildStationMediaDescription(context: Context, station: Station, metadata: String): MediaDescriptionCompat { val coverBitmap: Bitmap = ImageHelper.getScaledStationImage(context, station.image, Keys.SIZE_COVER_LOCK_SCREEN) val extras: Bundle = Bundle() extras.putParcelable(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, coverBitmap) @@ -468,8 +468,7 @@ object CollectionHelper { setMediaId(station.uuid) setIconBitmap(coverBitmap) setTitle(station.name) - setSubtitle(station.name) // metadata - //setDescription(episode.podcastName) + setSubtitle(metadata) setExtras(extras) }.build() } diff --git a/app/src/main/java/org/y20k/transistor/helpers/NotificationHelper.kt b/app/src/main/java/org/y20k/transistor/helpers/NotificationHelper.kt index 7ae2d4b4..effc84dd 100644 --- a/app/src/main/java/org/y20k/transistor/helpers/NotificationHelper.kt +++ b/app/src/main/java/org/y20k/transistor/helpers/NotificationHelper.kt @@ -67,9 +67,9 @@ class NotificationHelper(private val context: Context, sessionToken: MediaSessio setUsePlayPauseActions(true) setControlDispatcher(Dispatcher()) setUseStopAction(true) // set true to display the dismiss button - setUsePreviousAction(false) + setUsePreviousAction(true) setUsePreviousActionInCompactView(false) - setUseNextAction(false) + setUseNextAction(true) // only visible, if player is set to Player.REPEAT_MODE_ALL setUseNextActionInCompactView(false) setUseChronometer(true) } @@ -103,6 +103,14 @@ class NotificationHelper(private val context: Context, sessionToken: MediaSessio mediaController.sendCommand(Keys.CMD_DISMISS_NOTIFICATION, null, null) return true } + override fun dispatchPrevious(player: Player): Boolean { + mediaController.sendCommand(Keys.CMD_PREVIOUS_STATION, null, null) + return true + } + override fun dispatchNext(player: Player): Boolean { + mediaController.sendCommand(Keys.CMD_NEXT_STATION, null, null) + return true + } } /* * End of inner class @@ -149,6 +157,6 @@ class NotificationHelper(private val context: Context, sessionToken: MediaSessio } } /* - * End of inner class - */ + * End of inner class + */ } diff --git a/app/src/main/java/org/y20k/transistor/playback/PlayerService.kt b/app/src/main/java/org/y20k/transistor/playback/PlayerService.kt index ef3e0ec3..8fc40281 100644 --- a/app/src/main/java/org/y20k/transistor/playback/PlayerService.kt +++ b/app/src/main/java/org/y20k/transistor/playback/PlayerService.kt @@ -30,6 +30,7 @@ import android.support.v4.media.MediaBrowserCompat import android.support.v4.media.MediaDescriptionCompat import android.support.v4.media.session.MediaSessionCompat import android.support.v4.media.session.PlaybackStateCompat +import android.view.KeyEvent import android.widget.Toast import androidx.core.content.ContextCompat import androidx.core.os.bundleOf @@ -101,6 +102,7 @@ class PlayerService(): MediaBrowserServiceCompat() { SimpleExoPlayer.Builder(this).build().apply { setAudioAttributes(attributes, true) setHandleAudioBecomingNoisy(true) + setRepeatMode(Player.REPEAT_MODE_ALL) addListener(playerListener) addMetadataOutput(metadataOutput) addAnalyticsListener(analyticsListener) @@ -136,20 +138,20 @@ class PlayerService(): MediaBrowserServiceCompat() { // ExoPlayer manages MediaSession mediaSessionConnector = MediaSessionConnector(mediaSession) mediaSessionConnector.setPlaybackPreparer(preparer) - //mediaSessionConnector.setMediaButtonEventHandler(buttonEventHandler) + mediaSessionConnector.setMediaButtonEventHandler(buttonEventHandler) //mediaSessionConnector.setMediaMetadataProvider(metadataProvider) mediaSessionConnector.setQueueNavigator(object : TimelineQueueNavigator(mediaSession) { override fun getMediaDescription(player: Player, windowIndex: Int): MediaDescriptionCompat { // create media description - used in notification - return CollectionHelper.buildStationMediaDescription(this@PlayerService, station) + return CollectionHelper.buildStationMediaDescription(this@PlayerService, station, metadataHistory.last()) } override fun onSkipToPrevious(player: Player, controlDispatcher: ControlDispatcher) { - station = CollectionHelper.getPreviousStation(collection, station.uuid) - preparePlayer(true) + LogHelper.d(TAG, "onSkipToPrevious called") // todo remove + skipToPreviousStation() } override fun onSkipToNext(player: Player, controlDispatcher: ControlDispatcher) { - station = CollectionHelper.getNextStation(collection, station.uuid) - preparePlayer(true) + LogHelper.d(TAG, "onSkipToNext called") // todo remove + skipToNextStation() } }) @@ -239,6 +241,9 @@ class PlayerService(): MediaBrowserServiceCompat() { metadataHistory.removeAt(0) } // update notification + // see: https://github.com/google/ExoPlayer/issues/5494#issuecomment-462476576 + mediaSessionConnector.invalidateMediaSessionQueue() + mediaSessionConnector.invalidateMediaSessionMetadata() notificationHelper.updateNotification() // save history PreferencesHelper.saveMetadataHistory(this, metadataHistory) @@ -303,7 +308,10 @@ class PlayerService(): MediaBrowserServiceCompat() { collection = CollectionHelper.savePlaybackState(this, collection, station, playbackState) updatePlayerState(station, playbackState) // display notification (notification is hidden via CMD_DISMISS_NOTIFICATION) - if (player.isPlaying) notificationHelper.showNotificationForPlayer(player) + if (player.isPlaying) { + //player.seekTo(player.bufferedPosition) + notificationHelper.showNotificationForPlayer(player) + } } @@ -496,6 +504,27 @@ class PlayerService(): MediaBrowserServiceCompat() { PreferencesHelper.savePlayerState(this, playerState) } + + /* Load next station and start playback */ + private fun skipToNextStation() { + // stop current playback, if necessary + if (player.isPlaying) player.stop() + // get station start playback + station = CollectionHelper.getNextStation(collection, station.uuid) + preparer.onPrepare(true) + } + + + /* Load next station and start playback */ + private fun skipToPreviousStation() { + // stop current playback, if necessary + if (player.isPlaying) player.stop() + // get station start playback + station = CollectionHelper.getPreviousStation(collection, station.uuid) + preparer.onPrepare(true) + } + + /* * EventListener: Listener for ExoPlayer Events */ @@ -590,6 +619,30 @@ class PlayerService(): MediaBrowserServiceCompat() { */ + /* + * MediaButtonEventHandler: overrides headphone next/previous button behavior + */ + private val buttonEventHandler = object : MediaSessionConnector.MediaButtonEventHandler { + override fun onMediaButtonEvent(player: Player, controlDispatcher: ControlDispatcher, mediaButtonEvent: Intent): Boolean { + val event: KeyEvent? = mediaButtonEvent.getParcelableExtra(Intent.EXTRA_KEY_EVENT) + when (event?.keyCode) { + KeyEvent.KEYCODE_MEDIA_NEXT -> { + skipToNextStation() + return true + } + KeyEvent.KEYCODE_MEDIA_PREVIOUS -> { + skipToPreviousStation() + return true + } + else -> return false + } + } + } + /* + * End of declaration + */ + + // /* // * MediaMetadataProvider: creates metadata for currently playing station // */ @@ -713,6 +766,14 @@ class PlayerService(): MediaBrowserServiceCompat() { preparePlayer(true) return true } + Keys.CMD_PREVIOUS_STATION -> { + skipToPreviousStation() + return true + } + Keys.CMD_NEXT_STATION -> { + skipToNextStation() + return true + } Keys.CMD_DISMISS_NOTIFICATION -> { // pause playback and hide notification player.pause() @@ -743,5 +804,8 @@ class PlayerService(): MediaBrowserServiceCompat() { sendBroadcast(intent) } } + /* + * End of declaration + */ } diff --git a/app/src/main/java/org/y20k/transistor/ui/PlayerState.kt b/app/src/main/java/org/y20k/transistor/ui/PlayerState.kt index fcf06efd..8f277294 100644 --- a/app/src/main/java/org/y20k/transistor/ui/PlayerState.kt +++ b/app/src/main/java/org/y20k/transistor/ui/PlayerState.kt @@ -18,7 +18,7 @@ import android.os.Parcelable import android.support.v4.media.session.PlaybackStateCompat import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.gson.annotations.Expose -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize import org.y20k.transistor.Keys