Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 0 additions & 23 deletions app/src/debug/res/xml-v25/shortcuts.xml

This file was deleted.

10 changes: 10 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,16 @@
android:exported="false"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />

<!-- Invisible trampoline for the "Resume playback" launcher shortcut (#508): forwards a
resume command to MusicService and finishes, so playback resumes without opening the UI. -->
<activity
android:name=".ResumePlaybackActivity"
android:exported="true"
android:excludeFromRecents="true"
android:noHistory="true"
android:taskAffinity=""
android:theme="@android:style/Theme.NoDisplay" />

<service
android:name=".playback.MusicService"
android:exported="true"
Expand Down
25 changes: 25 additions & 0 deletions app/src/main/kotlin/com/jtech/zemer/ResumePlaybackActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.jtech.zemer

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import com.jtech.zemer.playback.MusicService
import com.jtech.zemer.utils.reportException

/**
* Invisible trampoline for the "Resume playback" launcher shortcut (#508). A launcher shortcut can
* only launch an Activity, so this no-display Activity forwards a resume command to [MusicService]
* and finishes immediately - playback resumes in the background without opening the app UI. Started
* from a user tap (foreground), so the plain service start is permitted; [MusicService] promotes
* itself to the foreground once playback begins (the same path the widget's play control uses).
*/
class ResumePlaybackActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val intent = Intent(this, MusicService::class.java).setAction(MusicService.ACTION_RESUME_PLAYBACK)
runCatching { startService(intent) }
.recoverCatching { startForegroundService(intent) }
.onFailure { reportException(it) }
finish()
}
}
25 changes: 25 additions & 0 deletions app/src/main/kotlin/com/jtech/zemer/playback/MusicService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ import com.metrolist.innertube.models.SongItem
import com.metrolist.innertube.models.WatchEndpoint
import com.jtech.zemer.MainActivity
import com.jtech.zemer.R
import com.jtech.zemer.extensions.toast
import com.jtech.zemer.constants.AndroidAutoTargetPlaylistKey
import com.jtech.zemer.constants.AudioNormalizationKey
import com.jtech.zemer.constants.PlaybackMode
Expand Down Expand Up @@ -163,6 +164,7 @@ import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
Expand Down Expand Up @@ -2823,6 +2825,23 @@ class MusicService :

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) {
ACTION_RESUME_PLAYBACK -> {
// Resume in the background from the launcher shortcut (#508) without opening the UI.
// On a cold start the persistent queue restores ASYNCHRONOUSLY (playQueue loads its
// items off the main thread and then pins playWhenReady=false), so wait for the queue
// to land AND leave STATE_IDLE - which means playQueue has finished its setup and can
// no longer re-pause us - before playing, instead of reading a still-empty player and
// firing a false "nothing to resume". Genuinely nothing to restore (Persistent Queue
// off / never played) times out into a brief toast. Media3 promotes the service to the
// foreground once playback starts. scope is Main, so player access here is safe.
scope.launch {
val ready = withTimeoutOrNull(RESUME_RESTORE_TIMEOUT_MS) {
while (player.mediaItemCount == 0 || player.playbackState == STATE_IDLE) delay(50)
true
} ?: false
if (ready) player.play() else toast(R.string.nothing_to_resume)
}
}
MusicWidget.ACTION_PLAY_PAUSE -> {
if (discoveryHandler.isConnected) {
// isRemotePlaying falls back to the play intent before the receiver's first state
Expand Down Expand Up @@ -2883,6 +2902,12 @@ class MusicService :
}

companion object {
// Background resume from the launcher shortcut (#508): ResumePlaybackActivity starts the
// service with this action and finishes, so playback resumes without opening the app UI.
const val ACTION_RESUME_PLAYBACK = "com.jtech.zemer.action.RESUME_PLAYBACK"
// How long the resume shortcut waits for the async persistent-queue restore before giving up
// and toasting "nothing to resume".
private const val RESUME_RESTORE_TIMEOUT_MS = 5_000L
const val ROOT = "root"
const val SONG = "song"
const val ARTIST = "artist"
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/res/drawable/shortcut_resume.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24.0dip"
android:height="24.0dip"
android:viewportWidth="960.0"
android:viewportHeight="960.0">
<path
android:fillColor="#000000"
android:pathData="M320,760v-560l440,280 -440,280Z" />
</vector>
2 changes: 2 additions & 0 deletions app/src/main/res/values/metrolist_strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<resources>
<string name="pause">Pause</string>
<string name="replay">Replay</string>
<string name="resume_playback">Resume playback</string>
<string name="nothing_to_resume">Nothing to resume</string>
<string name="crop_album_art">Crop album art</string>
<string name="crop_album_art_desc">Fill the player with the cover art instead of fitting it (edges may be cropped)</string>
<string name="not_applicable">N/A</string>
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/res/xml-v25/shortcuts.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:enabled="true"
android:icon="@drawable/shortcut_resume"
android:shortcutId="resume"
android:shortcutShortLabel="@string/resume_playback">
<intent
android:action="com.jtech.zemer.action.RESUME"
android:targetClass="com.jtech.zemer.ResumePlaybackActivity"
android:targetPackage="com.jtech.zemer" />
</shortcut>
<shortcut
android:enabled="true"
android:icon="@drawable/shortcut_search"
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/kotlin-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ Every tracked Kotlin file is listed with hard metadata extracted from the file t
| `app/src/main/kotlin/com/jtech/zemer/playback/MediaLibrarySessionCallback.kt` | 816 | `com.jtech.zemer.playback` | no | 60 | 80 | android.content, android.net, android.os, androidx.annotation, androidx.core, androidx.media3, com.google, dagger.hilt, javax.inject, kotlinx.coroutines |
| `app/src/main/kotlin/com/jtech/zemer/playback/MediaStoreDownloadManager.kt` | 1085 | `com.jtech.zemer.playback` | no | 55 | 127 | android.content, android.media, android.net, androidx.core, dagger.hilt, java.io, java.time, java.util, javax.inject, kotlin.math, kotlinx.coroutines, okhttp3.OkHttpClient, okhttp3.Request, timber.log |
| `app/src/main/kotlin/com/jtech/zemer/playback/MediaStoreDownloadService.kt` | 302 | `com.jtech.zemer.playback` | no | 27 | 48 | android.app, android.content, android.os, androidx.core, dagger.hilt, javax.inject, kotlin.math, kotlinx.coroutines, timber.log |
| `app/src/main/kotlin/com/jtech/zemer/playback/MusicService.kt` | 2917 | `com.jtech.zemer.playback` | no | 185 | 305 | android.app, android.content, android.database, android.media, android.net, android.os, androidx.core, androidx.datastore, androidx.media3, com.zemer, dagger.hilt, java.io, java.time, java.util, javax.inject, kotlin.time, kotlinx.coroutines, okhttp3.OkHttpClient, org.fcast, timber.log |
| `app/src/main/kotlin/com/jtech/zemer/playback/MusicService.kt` | 2942 | `com.jtech.zemer.playback` | no | 187 | 308 | android.app, android.content, android.database, android.media, android.net, android.os, androidx.core, androidx.datastore, androidx.media3, com.zemer, dagger.hilt, java.io, java.time, java.util, javax.inject, kotlin.time, kotlinx.coroutines, okhttp3.OkHttpClient, org.fcast, timber.log |
| `app/src/main/kotlin/com/jtech/zemer/playback/PlaybackNonceRegistry.kt` | 76 | `com.jtech.zemer.playback` | no | 1 | 13 | |
| `app/src/main/kotlin/com/jtech/zemer/playback/PlaybackProbe.kt` | 31 | `com.jtech.zemer.playback` | no | 0 | 8 | |
| `app/src/main/kotlin/com/jtech/zemer/playback/PlayerConnection.kt` | 413 | `com.jtech.zemer.playback` | no | 30 | 71 | android.content, androidx.media3, kotlinx.coroutines, org.fcast |
Expand Down
8 changes: 4 additions & 4 deletions docs/reference/non-kotlin-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ Every tracked non-Kotlin path outside `docs/` is listed. Text files report line
| `app/schemas/com.jtech.zemer.db.InternalDatabase/8.json` | 766 lines | text `.json`; JSON keys `formatVersion, database` |
| `app/schemas/com.jtech.zemer.db.InternalDatabase/9.json` | 840 lines | text `.json`; JSON keys `formatVersion, database` |
| `app/src/debug/res/values/app_name.xml` | 4 lines | text `.xml`; XML root `resources` |
| `app/src/debug/res/xml-v25/shortcuts.xml` | 23 lines | text `.xml`; XML root `shortcuts` |
| `app/src/main/AndroidManifest.xml` | 300 lines | text `.xml`; XML root `manifest` |
| `app/src/main/AndroidManifest.xml` | 310 lines | text `.xml`; XML root `manifest` |
| `app/src/main/assets/solver/astring.js` | 3 lines | text `.js` |
| `app/src/main/assets/solver/meriyah.js` | 9210 lines | text `.js` |
| `app/src/main/assets/solver/yt.solver.core.js` | 603 lines | text `.js` |
Expand Down Expand Up @@ -210,6 +209,7 @@ Every tracked non-Kotlin path outside `docs/` is listed. Text files report line
| `app/src/main/res/drawable/settings.xml` | 9 lines | text `.xml`; XML root `vector` |
| `app/src/main/res/drawable/share.xml` | 15 lines | text `.xml`; XML root `vector` |
| `app/src/main/res/drawable/shortcut_library.xml` | 15 lines | text `.xml`; XML root `vector` |
| `app/src/main/res/drawable/shortcut_resume.xml` | 9 lines | text `.xml`; XML root `vector` |
| `app/src/main/res/drawable/shortcut_search.xml` | 14 lines | text `.xml`; XML root `vector` |
| `app/src/main/res/drawable/shuffle.xml` | 9 lines | text `.xml`; XML root `vector` |
| `app/src/main/res/drawable/shuffle_on.xml` | 9 lines | text `.xml`; XML root `vector` |
Expand Down Expand Up @@ -264,11 +264,11 @@ Every tracked non-Kotlin path outside `docs/` is listed. Text files report line
| `app/src/main/res/values/app_name.xml` | 4 lines | text `.xml`; XML root `resources` |
| `app/src/main/res/values/colors.xml` | 9 lines | text `.xml`; XML root `resources` |
| `app/src/main/res/values/ic_launcher_background.xml` | 6 lines | text `.xml`; XML root `resources` |
| `app/src/main/res/values/metrolist_strings.xml` | 706 lines | text `.xml`; XML root `resources` |
| `app/src/main/res/values/metrolist_strings.xml` | 708 lines | text `.xml`; XML root `resources` |
| `app/src/main/res/values/strings.xml` | 437 lines | text `.xml`; XML root `resources` |
| `app/src/main/res/values/styles.xml` | 26 lines | text `.xml`; XML root `resources` |
| `app/src/main/res/values/values.xml` | 8 lines | text `.xml`; XML root `resources` |
| `app/src/main/res/xml-v25/shortcuts.xml` | 23 lines | text `.xml`; XML root `shortcuts` |
| `app/src/main/res/xml-v25/shortcuts.xml` | 33 lines | text `.xml`; XML root `shortcuts` |
| `app/src/main/res/xml/accessibility_service_config.xml` | 9 lines | text `.xml`; XML root `accessibility-service` |
| `app/src/main/res/xml/automotive_app_desc.xml` | 4 lines | text `.xml`; XML root `automotiveApp` |
| `app/src/main/res/xml/backup_rules.xml` | 12 lines | text `.xml`; XML root `full-backup-content` |
Expand Down
10 changes: 5 additions & 5 deletions docs/repository-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,7 @@ The following inventory is generated from repository files outside `.git`, `.gra
| `app/schemas/com.jtech.zemer.db.InternalDatabase/8.json` | 766 lines | `.json` |
| `app/schemas/com.jtech.zemer.db.InternalDatabase/9.json` | 840 lines | `.json` |
| `app/src/debug/res/values/app_name.xml` | 4 lines | `.xml` |
| `app/src/debug/res/xml-v25/shortcuts.xml` | 23 lines | `.xml` |
| `app/src/main/AndroidManifest.xml` | 300 lines | `.xml` |
| `app/src/main/AndroidManifest.xml` | 310 lines | `.xml` |
| `app/src/main/assets/solver/astring.js` | 3 lines | `.js` |
| `app/src/main/assets/solver/meriyah.js` | 9210 lines | `.js` |
| `app/src/main/assets/solver/yt.solver.core.js` | 603 lines | `.js` |
Expand Down Expand Up @@ -301,7 +300,7 @@ The following inventory is generated from repository files outside `.git`, `.gra
| `app/src/main/kotlin/com/jtech/zemer/playback/MediaLibrarySessionCallback.kt` | 816 lines | `.kt` |
| `app/src/main/kotlin/com/jtech/zemer/playback/MediaStoreDownloadManager.kt` | 1085 lines | `.kt` |
| `app/src/main/kotlin/com/jtech/zemer/playback/MediaStoreDownloadService.kt` | 302 lines | `.kt` |
| `app/src/main/kotlin/com/jtech/zemer/playback/MusicService.kt` | 2917 lines | `.kt` |
| `app/src/main/kotlin/com/jtech/zemer/playback/MusicService.kt` | 2942 lines | `.kt` |
| `app/src/main/kotlin/com/jtech/zemer/playback/PlaybackNonceRegistry.kt` | 76 lines | `.kt` |
| `app/src/main/kotlin/com/jtech/zemer/playback/PlaybackProbe.kt` | 31 lines | `.kt` |
| `app/src/main/kotlin/com/jtech/zemer/playback/PlayerConnection.kt` | 413 lines | `.kt` |
Expand Down Expand Up @@ -869,6 +868,7 @@ The following inventory is generated from repository files outside `.git`, `.gra
| `app/src/main/res/drawable/settings.xml` | 9 lines | `.xml` |
| `app/src/main/res/drawable/share.xml` | 15 lines | `.xml` |
| `app/src/main/res/drawable/shortcut_library.xml` | 15 lines | `.xml` |
| `app/src/main/res/drawable/shortcut_resume.xml` | 9 lines | `.xml` |
| `app/src/main/res/drawable/shortcut_search.xml` | 14 lines | `.xml` |
| `app/src/main/res/drawable/shuffle.xml` | 9 lines | `.xml` |
| `app/src/main/res/drawable/shuffle_on.xml` | 9 lines | `.xml` |
Expand Down Expand Up @@ -923,11 +923,11 @@ The following inventory is generated from repository files outside `.git`, `.gra
| `app/src/main/res/values/app_name.xml` | 4 lines | `.xml` |
| `app/src/main/res/values/colors.xml` | 9 lines | `.xml` |
| `app/src/main/res/values/ic_launcher_background.xml` | 6 lines | `.xml` |
| `app/src/main/res/values/metrolist_strings.xml` | 706 lines | `.xml` |
| `app/src/main/res/values/metrolist_strings.xml` | 708 lines | `.xml` |
| `app/src/main/res/values/strings.xml` | 437 lines | `.xml` |
| `app/src/main/res/values/styles.xml` | 26 lines | `.xml` |
| `app/src/main/res/values/values.xml` | 8 lines | `.xml` |
| `app/src/main/res/xml-v25/shortcuts.xml` | 23 lines | `.xml` |
| `app/src/main/res/xml-v25/shortcuts.xml` | 33 lines | `.xml` |
| `app/src/main/res/xml/accessibility_service_config.xml` | 9 lines | `.xml` |
| `app/src/main/res/xml/automotive_app_desc.xml` | 4 lines | `.xml` |
| `app/src/main/res/xml/backup_rules.xml` | 12 lines | `.xml` |
Expand Down
Loading