Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Sleep timer stops podcast after given time #3377

Merged
merged 19 commits into from
Dec 19, 2024
Merged

Conversation

mebarbosa
Copy link
Contributor

@mebarbosa mebarbosa commented Dec 17, 2024

Description

  • This is the alternative approach to fix the sleep timer issue. The first attempt was this one Fix: Sleep timer stops podcast after given time #3364 but we decided to not follow the approach of using Alarm clock for sleep timer.
  • This PR includes a refactor in Sleep Timer feature. This is not the final version of Sleep Timer, I can see a room for improvement, but I propose to do this in incremental parts.
  • Some important changes:
    • Moved the Sleep timer to PlaybackService instead of using Alarm Clock for it: 5f1e1ad
    • Extracted Sleep timer state to SleepTimer class: 727f292
    • Extracted the volume fade out to a new class f4f7e20. I also implemented the same logic that iOS

Fixes: #3355
Fixes: #2219

Testing Instructions

We need to regression test the whole sleep timer feature. I am going to describe all scenarios we should cover, but feel free to try more in case I miss anything.

Sleep Timer for X minutes

  • Smoke test the X minutes
  • ✅ Ensure sleep timer finishes in X minutes you set
  • ✅ Ensure you hear the fade out volume when there is about 5 seconds to end the sleep timer

Sleep Timer for end of episodes

  • Smoke test the end of episodes
  • ✅ Ensure you don't hear the fade out volume

Sleep Timer for end of chapter

  • Smoke test the end of chapter
  • Note: Every time we change to another chapter or change to another episode, we consider that we ended the episode, so it will decrease the remaining number of chapters left

Auto Restart Sleep Timer

Restarting timer

  1. Run the app
  2. Add 5 to 10 podcasts to your Up Next
  3. Play something
  4. Open the full player, tap the sleep timer icon (zZz), select X minutes (You can select 1 minute using the last option)
  5. After X minutes the playback should stop
  6. Tap play again
  7. ✅ The sleep timer should restart with X minutes you set
  8. You can also try this for end of episode and end of chapter

Not restarting timer

  • You can change the value of SleepTimer.MIN_TIME_TO_RESTART_SLEEP_TIMER_IN_MINUTES to 1 so you don't need to wait for 5 minutes
  1. Play something
  2. Open the full player, tap the sleep timer icon (zZz), select X minutes (You can select 1 minute using the last option)
  3. After X minutes the playback should stop
  4. Wait for 5 minutes. (or the time you set)
  5. Tap play again
  6. ✅ The sleep timer should not restart

Auto Restart Sleep Timer toggle disabled

  1. Open Sleep Timer setting
  2. Disable Auto Restart Sleep Timer toggle
  3. Repeat the steps from Restarting timer section above
  4. The sleep timer should not restart when this toggle is OFF ✅

Shake device to Restart

Restarting timer after shaking phone in foreground

  1. Run the app
  2. Play something
  3. Open the full player, tap the sleep timer icon (zZz), select X minutes
  4. Shake your device
  5. ✅ The sleep timer should restart with X minutes you set
  6. ✅ You should see the toast with Sleep timer restarted after device shake message

Restarting timer after shaking phone in background

  1. Run the app
  2. Play something
  3. Open the full player, tap the sleep timer icon (zZz), select X minutes
  4. Put your device in background
  5. Shake your device
  6. Go back to Pocket Casts App
  7. ✅ The sleep timer should have restarted with X minutes you set
  8. ✅ You should did not see the toast with Sleep timer restarted after device shake message
  9. ✅ The Sleep timer restart sound should have played

Do not restart timer after shaking phone

  1. Run the app
  2. Play something
  3. Open the full player, tap the sleep timer icon (zZz), select end of episode or chapter
  4. Shake your device
  5. ✅ The sleep timer should not restart
  6. Set Sleep timer for a X minutes
  7. Go to Sleep Timer settings and disable Shake to Restart Sleep Timer toggle
  8. Shake your device
  9. ✅ The sleep timer should not restart

Checklist

  • If this is a user-facing change, I have added an entry in CHANGELOG.md
  • Ensure the linter passes (./gradlew spotlessApply to automatically apply formatting/linting)
  • I have considered whether it makes sense to add tests for my changes
  • All strings that need to be localized are in modules/services/localization/src/main/res/values/strings.xml
  • Any jetpack compose components I added or changed are covered by compose previews
  • I have updated (or requested that someone edit) the spreadsheet to reflect any new or changed analytics.

@mebarbosa mebarbosa added [Type] Bug Not functioning as intended. [Project] Sleep Timer labels Dec 17, 2024
@mebarbosa mebarbosa added this to the 7.80 milestone Dec 17, 2024
@mebarbosa mebarbosa requested a review from a team as a code owner December 17, 2024 15:32
@mebarbosa mebarbosa requested review from geekygecko and removed request for a team December 17, 2024 15:32
@dangermattic
Copy link
Collaborator

dangermattic commented Dec 17, 2024

2 Warnings
⚠️ This PR is larger than 500 lines of changes. Please consider splitting it into smaller PRs for easier and faster reviews.
⚠️ Class PlayerVolumeFadeOut is missing tests, but unit-tests-exemption label was set to ignore this.

Generated by 🚫 Danger

@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Dec 17, 2024

📲 You can test the changes from this Pull Request in 📱 Mobile by scanning the QR code below to install the corresponding build.
App Name 📱 Mobile
Build TypedebugProd
Commit1850bda
Direct Downloadpocketcasts-app-prototype-build-pr3377-1850bda.apk
📲 You can test the changes from this Pull Request in 🚗 Automotive by scanning the QR code below to install the corresponding build.
App Name 🚗 Automotive
Build TypedebugProd
Commit1850bda
Direct Downloadpocketcasts-automotive-prototype-build-pr3377-1850bda.apk
📲 You can test the changes from this Pull Request in ⌚ Wear by scanning the QR code below to install the corresponding build.
App Name ⌚ Wear
Build TypedebugProd
Commit1850bda
Direct Downloadpocketcasts-wear-prototype-build-pr3377-1850bda.apk

Comment on lines +680 to +691
sleepTimer.stateFlow
.map { it }
.distinctUntilChanged()
.flowOn(Dispatchers.IO)
.onEach { state ->
onSleepTimerStateChange(state)
}
.catch { throwable ->
Timber.e(throwable, "Error observing SleepTimer state")
}
.launchIn(this)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking to observe the sleep timer changes by using sleeptimer.stateFlow.collect {} instead, but searching for the best approach I found this one.

The collect { } is suspending, which means it will block the current coroutine until the flow is canceled.

Comment on lines 191 to 198
private fun shouldRestartSleepEndOfEpisode(
diffTime: Duration,
currentEpisodeUuid: String,
isSleepEndOfEpisodeRunning: Boolean,
) = diffTime < MIN_TIME_TO_RESTART_SLEEP_TIMER_IN_MINUTES && !sleepTimerHistory.lastEpisodeUuidAutomaticEnded.isNullOrEmpty() && currentEpisodeUuid != sleepTimerHistory.lastEpisodeUuidAutomaticEnded && !isSleepEndOfEpisodeRunning

private fun shouldRestartSleepEndOfChapter(diffTime: Duration, isSleepEndOfChapterRunning: Boolean) = diffTime < MIN_TIME_TO_RESTART_SLEEP_TIMER_IN_MINUTES && !isSleepEndOfChapterRunning && sleepTimerHistory.lastSleepAfterEndOfChapterTime != null

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not change the logic of these functions. I just moved them to the bottom of the class since they are both private

Comment on lines 170 to 173
sleepTimerHistory = sleepTimerHistory.copy(
lastEpisodeUuidAutomaticEnded = uuid,
lastTimeSleepTimeHasFinished = System.currentTimeMillis().milliseconds,
)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change here is that now we have the sleepTimerHistory, instead of having separated variables like lastEpisodeUuidAutomaticEnded and lastTimeSleepTimeHasFinished

Comment on lines 181 to 184
sleepTimerHistory = sleepTimerHistory.copy(
lastSleepAfterEndOfChapterTime = time,
lastTimeSleepTimeHasFinished = time,
)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change here is that now we have the sleepTimerHistory, instead of having separated variables like lastSleepAfterEndOfChapterTime and lastTimeSleepTimeHasFinished

LogBuffer.i(TAG, "Added extra time: $minutes")
createAlarm(time.timeInMillis)

LogBuffer.i(TAG, "Added extra time: $newTimeLeft")
}

fun restartTimerIfIsRunning(onSuccess: () -> Unit): Duration? {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I simplified this method by removing all the callbacks since we now have the state within sleep timer

@CookieyedCodes
Copy link

CookieyedCodes commented Dec 17, 2024

Can I make a small suggestion,
If I long press on a certain sleep timer tital,
it would load a preset timer based of settings or resets the number back to 1?, hitting the +&- is a tad annoying and it would be a nice enhancement and appreciated imo :)

@@ -2329,9 +2267,6 @@ open class PlaybackManager @Inject constructor(
if (isPlaying()) {
statsManager.addTotalListeningTime(UPDATE_TIMER_POLL_TIME)
}
if (playbackStateRelay.blockingFirst().isSleepTimerRunning && !isSleepAfterEpisodeEnabled()) { // Does not apply to end of episode sleep time
setupFadeOutWhenFinishingSleepTimer()
}
verifySleepTimeForEndOfChapter()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could be wrong but would it be better to move the sleep timer logic and state into the SleepTimer class. Changing the code to something like the following:

Suggested change
verifySleepTimeForEndOfChapter()
val playbackState = playbackStateRelay.blockingFirst()
if (sleepTimer.shouldPause(playbackState = playbackState)) {
pause(sourceView = SourceView.AUTO_PAUSE)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@geekygecko I like this suggestion and I was trying to push this refactor today, but I am facing with more issues because the PlaybackManager.setupUpdateTimer (the place we also verify end of chapter) using RxJava and some methods of this end of chapter code uses Coroutine.

Could we move this refactor to other PR? It would make easier to test and also development. Every refactoring I am doing I need to smoke test the whole sleep timer just because there are a lot of changes here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I have approved the PR. Feel free to do any refactoring in another PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries. I created this issue for this refactor #3384

@mebarbosa
Copy link
Contributor Author

Can I make a small suggestion, If I long press on a certain sleep timer tital, it would load a preset timer based of settings or resets the number back to 1?, hitting the +&- is a tad annoying and it would be a nice enhancement and appreciated imo :)

Thank you for your suggestion, @CookieyedCodes . I can't not address anything new here and I would also need to check with our designs first, but I will share with them

@mebarbosa mebarbosa requested a review from geekygecko December 18, 2024 17:08
@CookieyedCodes
Copy link

No bothers, just a suggestion 😉

@mebarbosa mebarbosa enabled auto-merge (squash) December 19, 2024 12:05
@mebarbosa mebarbosa merged commit cf564f9 into main Dec 19, 2024
14 checks passed
@mebarbosa mebarbosa deleted the task/fix/sleep-timer branch December 19, 2024 12:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Sleep timer stops podcast **after** given time Sleep timer gets randomly cancelled
5 participants