Skip to content
This repository has been archived by the owner on Aug 27, 2024. It is now read-only.

Commit

Permalink
Implement PlayerNotificationManager and MediaSessionConnector
Browse files Browse the repository at this point in the history
  • Loading branch information
y20k committed Feb 19, 2021
1 parent 7e032ea commit 7ea3917
Show file tree
Hide file tree
Showing 16 changed files with 516 additions and 459 deletions.
2 changes: 2 additions & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Czech version: [Paper Mountain Studio ](https://github.com/PaperMountainStudio)

Dutch version: [hypothermic](https://github.com/hypothermic) | [weblate version history](https://hosted.weblate.org/changes/?lang=nl&project=transistor)

Esperanto version: [Jakub Fabijan](https://hosted.weblate.org/user/JakubFabijan/) | [weblate version history](https://hosted.weblate.org/changes/?lang=eo&project=transistor)

French version: [M2ck](https://github.com/M2ck), [ButterflyOfFire](https://github.com/BoFFire) | [weblate version history](https://hosted.weblate.org/changes/?lang=fr&project=transistor)

German version: [y20k](https://github.com/y20k) | [weblate version history](https://hosted.weblate.org/changes/?lang=de&project=transistor)
Expand Down
32 changes: 16 additions & 16 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-parcelize'


android {
Expand All @@ -13,7 +13,7 @@ android {
targetSdkVersion 30
versionCode 82
versionName '4.0.11'
resConfigs "en", "ar", "ca", "cs","de", "el", "es", "eu", "fr", "he", "hr", "id", "in", "it", "ja", "kab", "nb-rNO", "nl", "pa", "pl", "pt", "pt-rBR", "ru", "sk", "sl", "sr", "th", "tr", "uk", "zh-rCN"
resConfigs "en", "ar", "ca", "cs","de", "el", "eo","es", "eu", "fr", "he", "hr", "id", "in", "it", "ja", "kab", "nb-rNO", "nl", "pa", "pl", "pt", "pt-rBR", "ru", "sk", "sl", "sr", "th", "tr", "uk", "zh-rCN"
}

kotlinOptions {
Expand Down Expand Up @@ -62,31 +62,31 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])

implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"

implementation "com.google.android.material:material:1.2.1"
def coroutinesVersion = "1.3.9"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"

implementation "androidx.activity:activity-ktx:1.1.0"
implementation "com.google.android.material:material:1.3.0"
implementation "android.arch.work:work-runtime-ktx:1.0.1"

implementation "androidx.activity:activity-ktx:1.2.0"
implementation "androidx.appcompat:appcompat:1.2.0"
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
implementation "androidx.core:core-ktx:1.3.2"
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.2'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.2'
implementation "androidx.palette:palette-ktx:1.0.0"
implementation "androidx.preference:preference-ktx:1.1.1"
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'

implementation "android.arch.work:work-runtime-ktx:1.0.1"
def navigationVersion = "2.3.3"
implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion"
implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion"

implementation "com.google.android.exoplayer:exoplayer:2.12.3"
implementation "com.google.android.exoplayer:extension-mediasession:2.12.3"
implementation "com.google.code.gson:gson:2.8.6"
def exoplayerVersion = "2.13.0"
implementation "com.google.android.exoplayer:exoplayer:$exoplayerVersion"
implementation "com.google.android.exoplayer:extension-mediasession:$exoplayerVersion"

implementation "com.google.code.gson:gson:2.8.6"
implementation 'com.android.volley:volley:1.1.1'

}

androidExtensions {
experimental = true
}
4 changes: 2 additions & 2 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@
# Preserve the core classes - because they need to be de-/serialized with GSON
-keep public class org.y20k.transistor.core.** { *; }

-keep public class org.y20k.transistor.PlayerService { *; }
-keep public class org.y20k.transistor.playback.PlayerService { *; }

-keep public class org.y20k.transistor.search.RadioBrowserResult { *; }
-keep public class org.y20k.transistor.search.RadioBrowserResult { *; }
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@

<!-- Player Service -->
<service
android:name=".PlayerService"
android:name=".playback.PlayerService"
android:exported="true"
android:stopWithTask="false"
tools:ignore="ExportedService">
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/org/y20k/transistor/Keys.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ object Keys {
val PLAYBACK_SPEEDS = arrayOf(1.0f, 1.2f, 1.4f, 1.6f, 1.8f, 2.0f)

// notification
const val NOTIFICATION_NOW_PLAYING_ID: Int = 42
const val NOTIFICATION_NOW_PLAYING_CHANNEL: String = "notificationChannelIdPlaybackChannel"
const val NOW_PLAYING_NOTIFICATION_ID: Int = 42
const val NOW_PLAYING_NOTIFICATION_CHANNEL_ID: String = "notificationChannelIdPlaybackChannel"

// intent actions
const val ACTION_SHOW_PLAYER: String = "org.y20k.transistor.action.SHOW_PLAYER"
Expand Down
35 changes: 18 additions & 17 deletions app/src/main/java/org/y20k/transistor/PlayerFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import android.view.ViewGroup
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.core.view.isGone
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
Expand All @@ -56,6 +55,8 @@ import org.y20k.transistor.dialogs.FindStationDialog
import org.y20k.transistor.dialogs.YesNoDialog
import org.y20k.transistor.extensions.isActive
import org.y20k.transistor.helpers.*
import org.y20k.transistor.playback.PlayerController
import org.y20k.transistor.playback.PlayerService
import org.y20k.transistor.ui.LayoutHolder
import org.y20k.transistor.ui.PlayerState
import java.util.*
Expand All @@ -81,6 +82,7 @@ class PlayerFragment: Fragment(), CoroutineScope,
private lateinit var collectionViewModel: CollectionViewModel
private lateinit var layout: LayoutHolder
private lateinit var collectionAdapter: CollectionAdapter
private lateinit var playerController: PlayerController
private var collection: Collection = Collection()
private var playerServiceConnected: Boolean = false
private var onboarding: Boolean = false
Expand Down Expand Up @@ -197,7 +199,7 @@ class PlayerFragment: Fragment(), CoroutineScope,
override fun onStop() {
super.onStop()
// (see "stay in sync with the MediaSession")
MediaControllerCompat.getMediaController(activity as Activity)?.unregisterCallback(mediaControllerCallback)
playerController.unregisterCallback(mediaControllerCallback)
mediaBrowser.disconnect()
playerServiceConnected = false
}
Expand Down Expand Up @@ -371,16 +373,16 @@ class PlayerFragment: Fragment(), CoroutineScope,

// set up sleep timer start button
layout.sheetSleepTimerStartButtonView.setOnClickListener {
val playbackState: PlaybackStateCompat = MediaControllerCompat.getMediaController(activity as Activity).playbackState
val playbackState: PlaybackStateCompat = playerController.getPlaybackState()
when (playbackState.isActive) {
true -> MediaControllerCompat.getMediaController(activity as Activity).sendCommand(Keys.CMD_START_SLEEP_TIMER, null, null)
true -> playerController.startSleepTimer()
false -> Toast.makeText(activity as Context, R.string.toastmessage_sleep_timer_unable_to_start, Toast.LENGTH_LONG).show()
}
}

// set up sleep timer cancel button
layout.sheetSleepTimerCancelButtonView.setOnClickListener {
MediaControllerCompat.getMediaController(activity as Activity).sendCommand(Keys.CMD_CANCEL_SLEEP_TIMER, null, null)
playerController.cancelSleepTimer()
layout.sleepTimerRunningViews.isGone = true
}

Expand All @@ -394,17 +396,14 @@ class PlayerFragment: Fragment(), CoroutineScope,
// get player state
playerState = PreferencesHelper.loadPlayerState(activity as Context)

// get reference to media controller
val mediaController = MediaControllerCompat.getMediaController(activity as Activity)

// main play/pause button
layout.playButtonView.setOnClickListener {
onPlayButtonTapped(playerState.stationUuid, playerState.playbackState)
// onPlayButtonTapped(playerState.stationUuid, mediaController.playbackState.state) todo remove
//onPlayButtonTapped(playerState.stationUuid, playerController.getPlaybackState().state) // todo remove
}

// register a callback to stay in sync
mediaController.registerCallback(mediaControllerCallback)
playerController.registerCallback(mediaControllerCallback)
}


Expand Down Expand Up @@ -442,8 +441,8 @@ class PlayerFragment: Fragment(), CoroutineScope,
layout.updatePlayerViews(activity as Context, station, playerState.playbackState)
// start / pause playback
when (startPlayback) {
true -> MediaControllerCompat.getMediaController(activity as Activity).transportControls.playFromMediaId(station.uuid, null)
false -> MediaControllerCompat.getMediaController(activity as Activity).transportControls.pause()
true -> playerController.play(station.uuid)
false -> playerController.pause()
}
}

Expand Down Expand Up @@ -500,13 +499,13 @@ class PlayerFragment: Fragment(), CoroutineScope,
private fun handleStartPlayer() {
val intent: Intent = (activity as Activity).intent
if (intent.hasExtra(Keys.EXTRA_START_LAST_PLAYED_STATION)) {
MediaControllerCompat.getMediaController(activity as Activity).transportControls.playFromMediaId(playerState.stationUuid, null)
playerController.play(playerState.stationUuid)
} else if (intent.hasExtra(Keys.EXTRA_STATION_UUID)) {
val uuid: String? = intent.getStringExtra(Keys.EXTRA_STATION_UUID)
MediaControllerCompat.getMediaController(activity as Activity).transportControls.playFromMediaId(uuid, null)
val uuid: String = intent.getStringExtra(Keys.EXTRA_STATION_UUID) ?: String()
playerController.play(uuid)
} else if (intent.hasExtra(Keys.EXTRA_STREAM_URI)) {
val streamUri: String = intent.getStringExtra(Keys.EXTRA_STREAM_URI) ?: String()
MediaControllerCompat.getMediaController(activity as Activity).sendCommand(Keys.CMD_PLAY_STREAM, bundleOf(Pair(Keys.KEY_STREAM_URI, streamUri)), null)
playerController.playStreamDirectly(streamUri)
}
}

Expand Down Expand Up @@ -578,6 +577,8 @@ class PlayerFragment: Fragment(), CoroutineScope,
val mediaController = MediaControllerCompat(activity as Context, token)
// save the controller
MediaControllerCompat.setMediaController(activity as Activity, mediaController)
// initialize playerController
playerController = PlayerController(mediaController)
}
playerServiceConnected = true

Expand Down Expand Up @@ -664,7 +665,7 @@ class PlayerFragment: Fragment(), CoroutineScope,
private val periodicProgressUpdateRequestRunnable: Runnable = object : Runnable {
override fun run() {
// request current playback position
MediaControllerCompat.getMediaController(activity as Activity).sendCommand(Keys.CMD_REQUEST_PROGRESS_UPDATE, null, resultReceiver)
playerController.requestProgressUpdate(resultReceiver)
// use the handler to start runnable again after specified delay
handler.postDelayed(this, 500)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package org.y20k.transistor
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import org.y20k.transistor.playback.PlayerService


/*
Expand Down
21 changes: 21 additions & 0 deletions app/src/main/java/org/y20k/transistor/helpers/CollectionHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ package org.y20k.transistor.helpers

import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.net.Uri
import android.os.Bundle
import android.support.v4.media.MediaBrowserCompat
import android.support.v4.media.MediaDescriptionCompat
import android.support.v4.media.MediaMetadataCompat
Expand Down Expand Up @@ -456,6 +458,25 @@ object CollectionHelper {
}


/* Creates description for a single episode - used in MediaSessionConnector */
fun buildStationMediaDescription(context: Context, station: Station): 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)
extras.putParcelable(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, coverBitmap)
return MediaDescriptionCompat.Builder().apply {
setMediaId(station.uuid)
setIconBitmap(coverBitmap)
setTitle(station.name)
setSubtitle(station.name) // metadata
//setDescription(episode.podcastName)
setExtras(extras)
}.build()
}




/* Creates a fallback station - stupid hack for Android Auto compatibility :-/ */
fun createFallbackStation(): Station {
return Station(name = "KCSB", streamUris = mutableListOf("http://live.kcsb.org:80/KCSB_128"), streamContent = Keys.MIME_TYPE_MPEG)
Expand Down
Loading

0 comments on commit 7ea3917

Please sign in to comment.