feat: add system media controls for the active server

Add a media3 MediaSessionService that mirrors playback on the active
RelayTV server as a system media session (lock screen / quick settings):

- MediaControlService polls GET /status and publishes state, metadata,
  artwork, and remote volume; forwards play/pause, next/previous, seek,
  stop, and volume to the server HTTP API
- RelayRemotePlayer implements SimpleBasePlayer over the remote state
- RemoteStatus parses /status defensively across server versions
- New Settings screen (toolbar menu) with a media-controls toggle and a
  Manage servers shortcut; fix open_servers intent handling via
  onNewIntent when MainActivity is already running

Release prep:

- Bump to versionCode 6 / versionName 1.3.0
- Wire optional release signing from MYAPP_RELEASE_* gradle properties
- Add FOREGROUND_SERVICE(_MEDIA_PLAYBACK) permissions and lint opt-ins
- Update privacy policy, release checklist (Play Console foreground
  service declaration), README, and Gradle daemon heap

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 15:47:49 -05:00
co-authored by Claude Fable 5
parent 0967082e1c
commit 9f46eecf0e
16 changed files with 749 additions and 14 deletions
@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:title="@string/settings" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="8dp">
<LinearLayout
android:id="@+id/rowMediaControls"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:background="?attr/selectableItemBackground"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:paddingTop="14dp"
android:paddingBottom="14dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/media_controls_title"
android:textAppearance="?attr/textAppearanceTitleMedium"
android:textColor="@android:color/white" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="@string/media_controls_summary"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textColor="#B3FFFFFF" />
</LinearLayout>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switchMediaControls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/rowManageServers"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="?attr/selectableItemBackground"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:paddingTop="14dp"
android:paddingBottom="14dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/settings_manage_servers"
android:textAppearance="?attr/textAppearanceTitleMedium"
android:textColor="@android:color/white" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="@string/settings_manage_servers_summary"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textColor="#B3FFFFFF" />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
+4
View File
@@ -11,6 +11,10 @@
android:title="@string/reload"
android:icon="@android:drawable/ic_popup_sync"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_settings"
android:title="@string/settings"
app:showAsAction="never" />
<item
android:id="@+id/action_privacy"
android:title="@string/privacy_policy"
+5
View File
@@ -34,4 +34,9 @@
<string name="privacy_policy">Privacy policy</string>
<string name="privacy_policy_url">https://github.com/mcgeezy/relaytv-android/blob/main/docs/PRIVACY_POLICY.md</string>
<string name="privacy_open_failed">No browser available to open the privacy policy.</string>
<string name="settings">Settings</string>
<string name="media_controls_title">Media controls</string>
<string name="media_controls_summary">Show system media controls (lock screen and quick settings) for playback on the active server</string>
<string name="settings_manage_servers">Manage servers</string>
<string name="settings_manage_servers_summary">Add, edit, or switch RelayTV servers</string>
</resources>
+1 -1
View File
@@ -9,7 +9,7 @@
<item name="android:colorBackground">@color/relaytv_surface</item>
</style>
<style name="Theme.RelayTV.NoActionBar" parent="Theme.Material3.DayNight.NoActionBar" />
<style name="Theme.RelayTV.NoActionBar" parent="Theme.RelayTV" />
<style name="Theme.RelayTV.Share" parent="Theme.Material3.DayNight.NoActionBar">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>