Client: seek controls, Bazarr subtitle download and cast panel in the player; MDBList ratings strip; episode and schedule detail pages; series pace estimate; what's new panel; install-permission onboarding step; synced per-profile preferences; Emby outage banner. Gateway: rebuilt admin console (one fragment per page), preference history and restore, merged Continue Watching, Emby health probe, subtitle selection and Bazarr download, structured request logging with per-request identity, and embedded build version. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
191 lines
8.2 KiB
XML
191 lines
8.2 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<!-- <merge> rather than a FrameLayout root: setContentView already gives us a full-screen
|
|
FrameLayout to merge into, so this saves a redundant level in the view hierarchy. -->
|
|
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
|
|
<androidx.media3.ui.PlayerView
|
|
android:id="@+id/player_view"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="match_parent"
|
|
android:background="#000000"
|
|
app:auto_show="true"
|
|
app:controller_layout_id="@layout/memby_player_controls"
|
|
app:hide_on_touch="true"
|
|
app:keep_content_on_player_reset="true"
|
|
app:resize_mode="fit"
|
|
app:show_buffering="never"
|
|
app:surface_type="surface_view"
|
|
app:use_controller="true" />
|
|
|
|
<!-- Channel + programme identity. It is owned by the activity rather than the
|
|
controller, so it can appear briefly without opening the transport controls. -->
|
|
<include layout="@layout/player_playback_identity" />
|
|
|
|
<!-- Skipping. Declared before the timing cues so those sit above it: a lower third on
|
|
the left and this centred chip do not overlap, and if they ever do, the cue that
|
|
appears once matters more than one that appears on every press. -->
|
|
<include layout="@layout/player_seek_indicator" />
|
|
|
|
<!-- The ten-minute cue. Declared before the next-up banner so that banner covers it
|
|
if the two ever coincide: what comes next matters more than how long is left. -->
|
|
<include layout="@layout/player_time_remaining" />
|
|
|
|
<!-- Finale context has priority over the ordinary start cue and is physically stacked
|
|
above it. Both remain non-focusable lower thirds. -->
|
|
<include layout="@layout/player_season_finale" />
|
|
|
|
<!-- Above the video, below the loading overlay: a slide that is still starting has
|
|
nothing to say about what comes next. -->
|
|
<include layout="@layout/player_next_up_banner" />
|
|
|
|
<!-- App-owned subtitle controls. ExoPlayer supplies tracks and rendering only; this
|
|
overlay deliberately stays in Memby's TV design language. -->
|
|
<include layout="@layout/player_subtitle_overlay" />
|
|
|
|
<!-- Cast is metadata-only and never pauses or rebuilds the player. -->
|
|
<include layout="@layout/player_cast_overlay" />
|
|
|
|
<!-- Service alerts, the same bar the launcher shows. Declared before the loading and
|
|
error overlays so those cover it: a notice about the library is not what someone
|
|
staring at a failed stream needs. Never focusable, so it cannot take the remote
|
|
away from the transport controls. -->
|
|
<androidx.compose.ui.platform.ComposeView
|
|
android:id="@+id/player_service_alerts"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="wrap_content"
|
|
android:layout_gravity="top"
|
|
android:focusable="false" />
|
|
|
|
<LinearLayout
|
|
android:id="@+id/playback_loading"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="match_parent"
|
|
android:background="#F2050708"
|
|
android:focusable="false"
|
|
android:gravity="center"
|
|
android:orientation="vertical">
|
|
|
|
<ImageView
|
|
android:id="@+id/playback_loading_logo"
|
|
android:layout_width="82dp"
|
|
android:layout_height="70dp"
|
|
android:contentDescription="@string/playback_loading"
|
|
android:scaleType="fitCenter"
|
|
android:src="@drawable/emby_logo" />
|
|
|
|
<TextView
|
|
android:id="@+id/playback_loading_title"
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content"
|
|
android:layout_marginTop="18dp"
|
|
android:text="@string/playback_loading"
|
|
android:textColor="#FFFFFFFF"
|
|
android:textSize="20sp"
|
|
android:textStyle="bold" />
|
|
|
|
<TextView
|
|
android:id="@+id/playback_loading_hint"
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content"
|
|
android:layout_marginTop="7dp"
|
|
android:text="@string/playback_loading_hint"
|
|
android:textColor="#FF929AA0"
|
|
android:textSize="14sp" />
|
|
</LinearLayout>
|
|
|
|
<!-- The existing PlayerView is temporarily hosted inside this overlay, then returned
|
|
here at full-screen size after the countdown. No second player or stream is used. -->
|
|
<include layout="@layout/player_preroll" />
|
|
|
|
<!-- Deliberately outside PlayerView's controller hierarchy: a fatal error must remain
|
|
actionable after Media3 hides its transport controls. -->
|
|
<FrameLayout
|
|
android:id="@+id/playback_error"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="match_parent"
|
|
android:background="#F2050708"
|
|
android:focusable="false"
|
|
android:visibility="gone">
|
|
|
|
<LinearLayout
|
|
android:layout_width="600dp"
|
|
android:layout_height="wrap_content"
|
|
android:layout_gravity="center"
|
|
android:gravity="center_horizontal"
|
|
android:orientation="vertical"
|
|
android:padding="36dp">
|
|
|
|
<ImageView
|
|
android:layout_width="72dp"
|
|
android:layout_height="62dp"
|
|
android:contentDescription="@null"
|
|
android:scaleType="fitCenter"
|
|
android:src="@drawable/emby_logo" />
|
|
|
|
<TextView
|
|
android:id="@+id/playback_error_title"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="wrap_content"
|
|
android:layout_marginTop="24dp"
|
|
android:gravity="center"
|
|
android:textColor="#FFFFFFFF"
|
|
android:textSize="28sp"
|
|
android:textStyle="bold" />
|
|
|
|
<TextView
|
|
android:id="@+id/playback_error_detail"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="wrap_content"
|
|
android:layout_marginTop="10dp"
|
|
android:gravity="center"
|
|
android:lineSpacingExtra="3dp"
|
|
android:textColor="#BFFFFFFF"
|
|
android:textSize="17sp" />
|
|
|
|
<LinearLayout
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content"
|
|
android:layout_marginTop="28dp"
|
|
android:orientation="horizontal">
|
|
|
|
<Button
|
|
android:id="@+id/playback_error_retry"
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content"
|
|
android:background="@drawable/next_up_primary_button"
|
|
android:focusable="true"
|
|
android:minWidth="0dp"
|
|
android:paddingStart="28dp"
|
|
android:paddingTop="10dp"
|
|
android:paddingEnd="28dp"
|
|
android:paddingBottom="10dp"
|
|
android:stateListAnimator="@null"
|
|
android:text="@string/playback_try_again"
|
|
android:textAllCaps="false"
|
|
android:textColor="@color/next_up_button_text"
|
|
android:textSize="16sp" />
|
|
|
|
<Button
|
|
android:id="@+id/playback_error_exit"
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content"
|
|
android:layout_marginStart="12dp"
|
|
android:background="@drawable/next_up_secondary_button"
|
|
android:focusable="true"
|
|
android:minWidth="0dp"
|
|
android:paddingStart="28dp"
|
|
android:paddingTop="10dp"
|
|
android:paddingEnd="28dp"
|
|
android:paddingBottom="10dp"
|
|
android:stateListAnimator="@null"
|
|
android:text="@string/playback_back_to_memby"
|
|
android:textAllCaps="false"
|
|
android:textColor="@color/next_up_button_text"
|
|
android:textSize="16sp" />
|
|
</LinearLayout>
|
|
</LinearLayout>
|
|
</FrameLayout>
|
|
|
|
</merge>
|