Skip to content

Commit ade6c5a

Browse files
authored
Merge pull request #22 from SoftwareEngineeringDaily/develop
Release v1.0.1
2 parents dff5d24 + 4e10998 commit ade6c5a

File tree

7 files changed

+117
-77
lines changed

7 files changed

+117
-77
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,4 @@ Interested in seeing a particular feature implemented in this app? Please open a
7676

7777
Contributing
7878
------------
79-
Checkout [CONTRIBUTING.md](https://github.com/SoftwareEngineeringDaily/software-engineering-daily-android/CONTRIBUTING.md) for details.
79+
Checkout [CONTRIBUTING.md](https://github.com/SoftwareEngineeringDaily/software-engineering-daily-android/blob/master/CONTRIBUTING.md) for details.

app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ android {
2020
applicationId "com.koalatea.sedaily"
2121
minSdkVersion 21
2222
targetSdkVersion 29
23-
versionCode 16
24-
versionName "1.0.0"
23+
versionCode 17
24+
versionName "1.0.1"
2525
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
2626
}
2727

app/src/main/java/com/koalatea/sedaily/feature/episodedetail/EpisodeDetailFragment.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ class EpisodeDetailFragment : BaseFragment() {
323323
is PlayerStatus.Playing -> showStopViews()
324324
is PlayerStatus.Paused -> showPlayViews()
325325
is PlayerStatus.Ended -> showPlayViews()
326+
is PlayerStatus.Cancelled -> showPlayViews()
326327
is PlayerStatus.Error -> acknowledgeGenericError()
327328
}
328329
}

app/src/main/java/com/koalatea/sedaily/feature/player/AudioService.kt

Lines changed: 88 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ class AudioService : LifecycleService() {
8888

8989
var episodeId: String? = null
9090
private set
91+
private var episodeTitle: String? = null
9192

9293
private lateinit var exoPlayer: SimpleExoPlayer
9394

@@ -117,75 +118,18 @@ class AudioService : LifecycleService() {
117118
.build()
118119
exoPlayer.setAudioAttributes(audioAttributes, true)
119120

121+
// Monitor ExoPlayer events.
120122
exoPlayer.addListener(PlayerEventListener())
121-
}
122-
123-
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
124-
handleIntent(intent)
125-
126-
return super.onStartCommand(intent, flags, startId)
127-
}
128-
129-
override fun onDestroy() {
130-
cancelPlaybackMonitor()
131-
132-
mediaSession?.release()
133-
mediaSessionConnector?.setPlayer(null)
134-
playerNotificationManager?.setPlayer(null)
135-
136-
exoPlayer.release()
137-
138-
super.onDestroy()
139-
}
140-
141-
@MainThread
142-
fun play(uri: Uri, startPosition: Long, playbackSpeed: Float? = null) {
143-
val userAgent = Util.getUserAgent(applicationContext, BuildConfig.APPLICATION_ID)
144-
val mediaSource = ExtractorMediaSource(
145-
uri,
146-
DefaultDataSourceFactory(applicationContext, userAgent),
147-
DefaultExtractorsFactory(),
148-
null,
149-
null)
150-
151-
val haveStartPosition = startPosition != C.POSITION_UNSET.toLong()
152-
if (haveStartPosition) {
153-
exoPlayer.seekTo(startPosition)
154-
}
155-
156-
playbackSpeed?.let { changePlaybackSpeed(playbackSpeed) }
157-
158-
exoPlayer.prepare(mediaSource, !haveStartPosition, false)
159-
exoPlayer.playWhenReady = true
160-
}
161-
162-
@MainThread
163-
fun resume() {
164-
exoPlayer.playWhenReady = true
165-
}
166-
167-
@MainThread
168-
fun pause() {
169-
exoPlayer.playWhenReady = false
170-
}
171-
172-
@MainThread
173-
fun changePlaybackSpeed(playbackSpeed: Float) {
174-
exoPlayer.playbackParameters = PlaybackParameters(playbackSpeed)
175-
}
176-
177-
@MainThread
178-
private fun handleIntent(intent: Intent?) {
179-
episodeId = intent?.getStringExtra(ARG_EPISODE_ID)
180123

124+
// Setup notification and media session.
181125
playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(
182126
applicationContext,
183127
PLAYBACK_CHANNEL_ID,
184128
R.string.playback_channel_name,
185129
PLAYBACK_NOTIFICATION_ID,
186130
object : PlayerNotificationManager.MediaDescriptionAdapter {
187131
override fun getCurrentContentTitle(player: Player): String {
188-
return intent?.getStringExtra(ARG_TITLE) ?: getString(R.string.loading_dots)
132+
return episodeTitle ?: getString(R.string.loading_dots)
189133
}
190134

191135
@Nullable
@@ -211,13 +155,28 @@ class AudioService : LifecycleService() {
211155
}
212156

213157
override fun onNotificationCancelled(notificationId: Int) {
158+
_playerStatusLiveData.value = PlayerStatus.Cancelled(episodeId)
159+
214160
stopSelf()
215161
}
162+
163+
override fun onNotificationPosted(notificationId: Int, notification: Notification?, ongoing: Boolean) {
164+
if (ongoing) {
165+
// Make sure the service will not get destroyed while playing media.
166+
startForeground(notificationId, notification)
167+
} else {
168+
// Make notification cancellable.
169+
stopForeground(false)
170+
}
171+
}
216172
}
217173
).apply {
218-
// omit skip previous and next actions
174+
// Omit skip previous and next actions.
219175
setUseNavigationActions(false)
220176

177+
// Add stop action.
178+
setUseStopAction(true)
179+
221180
val incrementMs = resources.getInteger(R.integer.increment_ms).toLong()
222181
setFastForwardIncrementMs(incrementMs)
223182
setRewindIncrementMs(incrementMs)
@@ -240,7 +199,7 @@ class AudioService : LifecycleService() {
240199
putParcelable(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, bitmap)
241200
}
242201

243-
val title = intent?.getStringExtra(ARG_TITLE) ?: getString(R.string.loading_dots)
202+
val title = episodeTitle ?: getString(R.string.loading_dots)
244203

245204
return MediaDescriptionCompat.Builder()
246205
.setIconBitmap(bitmap)
@@ -252,6 +211,30 @@ class AudioService : LifecycleService() {
252211

253212
setPlayer(exoPlayer)
254213
}
214+
}
215+
216+
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
217+
handleIntent(intent)
218+
219+
return super.onStartCommand(intent, flags, startId)
220+
}
221+
222+
override fun onDestroy() {
223+
cancelPlaybackMonitor()
224+
225+
mediaSession?.release()
226+
mediaSessionConnector?.setPlayer(null)
227+
playerNotificationManager?.setPlayer(null)
228+
229+
exoPlayer.release()
230+
231+
super.onDestroy()
232+
}
233+
234+
@MainThread
235+
private fun handleIntent(intent: Intent?) {
236+
episodeId = intent?.getStringExtra(ARG_EPISODE_ID)
237+
episodeTitle = intent?.getStringExtra(ARG_TITLE)
255238

256239
// Play
257240
intent?.let {
@@ -264,6 +247,42 @@ class AudioService : LifecycleService() {
264247
}
265248
}
266249

250+
@MainThread
251+
fun play(uri: Uri, startPosition: Long, playbackSpeed: Float? = null) {
252+
val userAgent = Util.getUserAgent(applicationContext, BuildConfig.APPLICATION_ID)
253+
val mediaSource = ExtractorMediaSource(
254+
uri,
255+
DefaultDataSourceFactory(applicationContext, userAgent),
256+
DefaultExtractorsFactory(),
257+
null,
258+
null)
259+
260+
val haveStartPosition = startPosition != C.POSITION_UNSET.toLong()
261+
if (haveStartPosition) {
262+
exoPlayer.seekTo(startPosition)
263+
}
264+
265+
playbackSpeed?.let { changePlaybackSpeed(playbackSpeed) }
266+
267+
exoPlayer.prepare(mediaSource, !haveStartPosition, false)
268+
exoPlayer.playWhenReady = true
269+
}
270+
271+
@MainThread
272+
fun resume() {
273+
exoPlayer.playWhenReady = true
274+
}
275+
276+
@MainThread
277+
fun pause() {
278+
exoPlayer.playWhenReady = false
279+
}
280+
281+
@MainThread
282+
fun changePlaybackSpeed(playbackSpeed: Float) {
283+
exoPlayer.playbackParameters = PlaybackParameters(playbackSpeed)
284+
}
285+
267286
@MainThread
268287
private fun saveLastListeningPosition() = lifecycleScope.launch {
269288
episodeId?.let { appDatabase.listenedDao().insert(Listened(it, exoPlayer.contentPosition, exoPlayer.duration)) }
@@ -321,18 +340,21 @@ class AudioService : LifecycleService() {
321340
if (playbackState == Player.STATE_READY) {
322341
if (exoPlayer.playWhenReady) {
323342
episodeId?.let { _playerStatusLiveData.value = PlayerStatus.Playing(it) }
324-
325-
monitorPlaybackProgress()
326343
} else {// Paused
327344
episodeId?.let { _playerStatusLiveData.value = PlayerStatus.Paused(it) }
328-
329-
cancelPlaybackMonitor()
330345
}
331346
} else if (playbackState == Player.STATE_ENDED) {
332347
episodeId?.let { _playerStatusLiveData.value = PlayerStatus.Ended(it) }
333348
} else {
334349
episodeId?.let { _playerStatusLiveData.value = PlayerStatus.Other(it) }
335350
}
351+
352+
// Only monitor playback to record progress when playing.
353+
if (playbackState == Player.STATE_READY && exoPlayer.playWhenReady) {
354+
monitorPlaybackProgress()
355+
} else {
356+
cancelPlaybackMonitor()
357+
}
336358
}
337359

338360
override fun onPlayerError(e: ExoPlaybackException?) {

app/src/main/java/com/koalatea/sedaily/feature/player/BasePlayerActivity.kt

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ abstract class BasePlayerActivity : AppCompatActivity(), PlayerCallback, Playbac
6666
_playerStatusLiveData.value = it
6767

6868
playerOverlayPlayMaterialButton.isSelected = it is PlayerStatus.Playing
69+
70+
if (it is PlayerStatus.Cancelled) {
71+
dismissPlayerOverlay()
72+
73+
stopAudioService()
74+
}
6975
})
7076

7177
// Show player after config change.
@@ -93,11 +99,6 @@ abstract class BasePlayerActivity : AppCompatActivity(), PlayerCallback, Playbac
9399

94100
setupPlayerBottomSheet()
95101

96-
// Show the player, if the audio service is already running.
97-
if (applicationContext.isServiceRunning(AudioService::class.java.name)) {
98-
bindToAudioService()
99-
}
100-
101102
playerOverlayPlayMaterialButton.setOnClickListener {
102103
if (playerOverlayPlayMaterialButton.isSelected) {
103104
audioService?.pause()
@@ -139,6 +140,17 @@ abstract class BasePlayerActivity : AppCompatActivity(), PlayerCallback, Playbac
139140
playerView.showController()
140141
}
141142

143+
override fun onStart() {
144+
super.onStart()
145+
146+
// Show the player, if the audio service is already running.
147+
if (applicationContext.isServiceRunning(AudioService::class.java.name)) {
148+
bindToAudioService()
149+
} else {
150+
dismissPlayerOverlay()
151+
}
152+
}
153+
142154
override fun onStop() {
143155
unbindAudioService()
144156

@@ -160,8 +172,12 @@ abstract class BasePlayerActivity : AppCompatActivity(), PlayerCallback, Playbac
160172
override fun stop() {
161173
dismissPlayerOverlay()
162174

175+
audioService?.episodeId?.let { episodeId ->
176+
_playerStatusLiveData.value = PlayerStatus.Paused(episodeId)
177+
} ?: run {
178+
_playerStatusLiveData.value = PlayerStatus.Other()
179+
}
163180
stopAudioService()
164-
_playerStatusLiveData.value = PlayerStatus.Other()
165181
}
166182

167183
private fun bindToAudioService() {

app/src/main/java/com/koalatea/sedaily/feature/player/PlayerStatus.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ sealed class PlayerStatus(open val episodeId: String?) {
44
data class Other(override val episodeId: String? = null) : PlayerStatus(episodeId)
55
data class Playing(override val episodeId: String) : PlayerStatus(episodeId)
66
data class Paused(override val episodeId: String) : PlayerStatus(episodeId)
7+
data class Cancelled(override val episodeId: String? = null) : PlayerStatus(episodeId)
78
data class Ended(override val episodeId: String) : PlayerStatus(episodeId)
89
data class Error(override val episodeId: String, val exception: Exception?) : PlayerStatus(episodeId)
910
}

app/src/main/res/layout/fragment_auth.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@
5151
android:layout_marginTop="16dp"
5252
android:layout_marginEnd="@dimen/first_keyline"
5353
android:layout_marginBottom="@dimen/first_keyline"
54-
android:textOff="@string/back_to_login_in"
55-
android:textOn="@string/back_to_create_account"
54+
android:textOff="@string/back_to_create_account"
55+
android:textOn="@string/back_to_login_in"
5656
app:layout_constraintBottom_toBottomOf="parent"
5757
app:layout_constraintEnd_toEndOf="parent"
5858
app:layout_constraintStart_toStartOf="parent"

0 commit comments

Comments
 (0)