mirror of
git://f0xx.org/android_cast
synced 2026-07-29 06:39:09 +03:00
git flow, infra descriptions, cast flow changes (mobile side)
This commit is contained in:
@@ -20,6 +20,7 @@ import android.text.TextUtils;
|
||||
import com.foxx.androidcast.display.ExternalDisplayCapturePolicy;
|
||||
import com.foxx.androidcast.receiver.av.ReceiverAudioPreset;
|
||||
import com.foxx.androidcast.receiver.av.ReceiverVideoPreset;
|
||||
import com.foxx.androidcast.sender.SenderResolutionUiMode;
|
||||
|
||||
/**
|
||||
* Global app preferences: identity, PIN, tray icon, and default sender/receiver cast options.
|
||||
@@ -52,6 +53,7 @@ public final class AppPreferences {
|
||||
|
||||
private static final String KEY_DEV_WIRED_DISPLAY_HINTS = "dev_wired_display_hints";
|
||||
private static final String KEY_DEV_USB_TETHER_TRANSPORT = "dev_usb_tether_transport";
|
||||
private static final String KEY_DEV_SENDER_RESOLUTION_UI_MODE = "dev_sender_resolution_ui_mode";
|
||||
private static final String KEY_DEV_EXTERNAL_CAPTURE_POLICY = "dev_external_capture_policy";
|
||||
private static final String KEY_DEV_COMMERCIAL_ADS = "dev_commercial_ads";
|
||||
private static final String KEY_DEV_COMMERCIAL_IAP = "dev_commercial_iap";
|
||||
@@ -330,6 +332,22 @@ public final class AppPreferences {
|
||||
prefs(context).edit().putBoolean(KEY_DEV_USB_TETHER_TRANSPORT, enabled).apply();
|
||||
}
|
||||
|
||||
/** Default: {@link SenderResolutionUiMode#SEPARATED}. */
|
||||
public static SenderResolutionUiMode getDevSenderResolutionUiMode(Context context) {
|
||||
String name = prefs(context).getString(
|
||||
KEY_DEV_SENDER_RESOLUTION_UI_MODE, SenderResolutionUiMode.SEPARATED.name());
|
||||
try {
|
||||
return SenderResolutionUiMode.valueOf(name);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return SenderResolutionUiMode.SEPARATED;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setDevSenderResolutionUiMode(Context context, SenderResolutionUiMode mode) {
|
||||
SenderResolutionUiMode value = mode != null ? mode : SenderResolutionUiMode.SEPARATED;
|
||||
prefs(context).edit().putString(KEY_DEV_SENDER_RESOLUTION_UI_MODE, value.name()).apply();
|
||||
}
|
||||
|
||||
public static ExternalDisplayCapturePolicy getExternalDisplayCapturePolicy(Context context) {
|
||||
return ExternalDisplayCapturePolicy.fromName(
|
||||
prefs(context).getString(KEY_DEV_EXTERNAL_CAPTURE_POLICY, null),
|
||||
|
||||
@@ -102,7 +102,9 @@ public class CastSettings implements Serializable {
|
||||
HD_720P(1.0f, 720, 0),
|
||||
FIXED_1080P(0f, 1080, 1920),
|
||||
FIXED_720P(0f, 720, 1280),
|
||||
ADAPTIVE(1.0f, 0, 0);
|
||||
ADAPTIVE(1.0f, 0, 0),
|
||||
/** Long-edge cap 480p (aspect preserved). */
|
||||
SD_480P(1.0f, 480, 0);
|
||||
|
||||
public final float scale;
|
||||
public final int maxHeight;
|
||||
|
||||
@@ -106,7 +106,7 @@ public final class CastSettingsBinder {
|
||||
bindVideoCodec(activity, settings, R.id.spinner_sender_video_codec);
|
||||
bindBitrate(activity, settings);
|
||||
bindNetworkAdoption(activity, settings, R.id.spinner_sender_network);
|
||||
bindSenderResolution(activity, settings);
|
||||
com.foxx.androidcast.sender.SenderResolutionUi.bindSenderResolution(activity, settings);
|
||||
bindCaptureMode(activity, settings);
|
||||
bindStreamProtection(activity, settings);
|
||||
bindAudioSpinner(activity, settings, R.id.spinner_sender_audio);
|
||||
@@ -323,27 +323,8 @@ public final class CastSettingsBinder {
|
||||
readEnumSpinner(activity.findViewById(spinnerId), settings::setNetworkAdoption);
|
||||
}
|
||||
|
||||
private static void bindSenderResolution(AppCompatActivity activity, CastSettings settings) {
|
||||
Spinner spinner = activity.findViewById(R.id.spinner_sender_resolution);
|
||||
CastSettings.Resolution[] modes = {
|
||||
CastSettings.Resolution.ADAPTIVE,
|
||||
CastSettings.Resolution.FIXED_1080P,
|
||||
CastSettings.Resolution.FIXED_720P,
|
||||
CastSettings.Resolution.FULL,
|
||||
CastSettings.Resolution.THREE_QUARTERS
|
||||
};
|
||||
String[] labels = {
|
||||
activity.getString(R.string.resolution_adaptive),
|
||||
activity.getString(R.string.resolution_fixed_1080p),
|
||||
activity.getString(R.string.resolution_fixed_720p),
|
||||
activity.getString(R.string.resolution_full),
|
||||
activity.getString(R.string.resolution_75)
|
||||
};
|
||||
bindEnumSpinner(spinner, modes, labels, settings.getResolution(), settings::setResolution);
|
||||
}
|
||||
|
||||
private static void readSenderResolution(AppCompatActivity activity, CastSettings settings) {
|
||||
readEnumSpinner(activity.findViewById(R.id.spinner_sender_resolution), settings::setResolution);
|
||||
com.foxx.androidcast.sender.SenderResolutionUi.readSenderResolution(activity, settings);
|
||||
}
|
||||
|
||||
public static void bindReceiverPip(AppCompatActivity activity) {
|
||||
@@ -551,7 +532,7 @@ public final class CastSettingsBinder {
|
||||
});
|
||||
}
|
||||
|
||||
private static <T extends Enum<T>> void bindEnumSpinner(Spinner spinner, T[] values, String[] labels,
|
||||
public static <T extends Enum<T>> void bindEnumSpinner(Spinner spinner, T[] values, String[] labels,
|
||||
T current, java.util.function.Consumer<T> onSelect) {
|
||||
spinner.setTag(R.id.tag_spinner_enum, values);
|
||||
SettingsStringArrayAdapter adapter = new SettingsStringArrayAdapter(spinner.getContext(), labels);
|
||||
@@ -581,7 +562,7 @@ public final class CastSettingsBinder {
|
||||
});
|
||||
}
|
||||
|
||||
private static <T extends Enum<T>> void readEnumSpinner(Spinner spinner,
|
||||
public static <T extends Enum<T>> void readEnumSpinner(Spinner spinner,
|
||||
java.util.function.Consumer<T> consumer) {
|
||||
Object tag = spinner.getTag(R.id.tag_spinner_enum);
|
||||
if (tag instanceof Enum[]) {
|
||||
|
||||
@@ -54,6 +54,9 @@ public final class CastTuningEngine {
|
||||
case HD_720P:
|
||||
capLong = 720;
|
||||
break;
|
||||
case SD_480P:
|
||||
capLong = 480;
|
||||
break;
|
||||
case FIXED_1080P:
|
||||
boxW = 1920;
|
||||
boxH = 1080;
|
||||
|
||||
@@ -34,6 +34,7 @@ import com.foxx.androidcast.ota.OtaUpdateCoordinator;
|
||||
import com.foxx.androidcast.receiver.av.ReceiverAudioPreset;
|
||||
import com.foxx.androidcast.receiver.av.ReceiverAvRuntime;
|
||||
import com.foxx.androidcast.receiver.av.ReceiverVideoPreset;
|
||||
import com.foxx.androidcast.sender.SenderResolutionUiMode;
|
||||
import com.foxx.androidcast.stats.ui.SessionStatsAnalyzerActivity;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
|
||||
@@ -88,6 +89,7 @@ public class DeveloperSettingsActivity extends AppCompatActivity {
|
||||
});
|
||||
|
||||
bindAvPresets();
|
||||
bindSenderResolutionUiModeSection();
|
||||
bindWiredDisplaySection();
|
||||
bindCommercialSection();
|
||||
}
|
||||
@@ -111,10 +113,37 @@ public class DeveloperSettingsActivity extends AppCompatActivity {
|
||||
findViewById(R.id.edit_ota_manifest_url),
|
||||
findViewById(R.id.check_ota_on_launch));
|
||||
bindAvPresets();
|
||||
bindSenderResolutionUiModeSection();
|
||||
bindWiredDisplaySection();
|
||||
bindCommercialSection();
|
||||
}
|
||||
|
||||
private void bindSenderResolutionUiModeSection() {
|
||||
Spinner spinner = findViewById(R.id.spinner_dev_sender_resolution_ui_mode);
|
||||
if (spinner == null) {
|
||||
return;
|
||||
}
|
||||
final SenderResolutionUiMode[] modes = SenderResolutionUiMode.values();
|
||||
String[] labels = {
|
||||
getString(R.string.dev_sender_resolution_ui_separated),
|
||||
getString(R.string.dev_sender_resolution_ui_legacy),
|
||||
getString(R.string.dev_sender_resolution_ui_combined),
|
||||
};
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
|
||||
android.R.layout.simple_spinner_item, labels);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinner.setAdapter(adapter);
|
||||
spinner.setOnItemSelectedListener(null);
|
||||
SenderResolutionUiMode current = AppPreferences.getDevSenderResolutionUiMode(this);
|
||||
spinner.setSelection(current.ordinal(), false);
|
||||
spinner.setOnItemSelectedListener(simpleSelection(() -> {
|
||||
int pos = spinner.getSelectedItemPosition();
|
||||
if (pos >= 0 && pos < modes.length) {
|
||||
AppPreferences.setDevSenderResolutionUiMode(this, modes[pos]);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void bindWiredDisplaySection() {
|
||||
TextView status = findViewById(R.id.text_wired_display_status);
|
||||
if (status != null) {
|
||||
|
||||
@@ -93,6 +93,8 @@ public final class CastDiagnosticsLabels {
|
||||
return "50% display scale";
|
||||
case HD_720P:
|
||||
return "720p long-edge cap";
|
||||
case SD_480P:
|
||||
return "480p long-edge cap";
|
||||
case FIXED_1080P:
|
||||
return "Fixed 1080p box";
|
||||
case FIXED_720P:
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.foxx.androidcast.sender;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import android.widget.Spinner;
|
||||
|
||||
import com.foxx.androidcast.AppPreferences;
|
||||
import com.foxx.androidcast.CastSettings;
|
||||
import com.foxx.androidcast.CastSettingsBinder;
|
||||
import com.foxx.androidcast.R;
|
||||
|
||||
/** Binds {@link R.id#spinner_sender_resolution} per {@link SenderResolutionUiMode}. */
|
||||
public final class SenderResolutionUi {
|
||||
private SenderResolutionUi() {}
|
||||
|
||||
public static SenderResolutionUiMode mode(Context context) {
|
||||
return AppPreferences.getDevSenderResolutionUiMode(context);
|
||||
}
|
||||
|
||||
public static void bindSenderResolution(AppCompatActivity activity, CastSettings settings) {
|
||||
Spinner spinner = activity.findViewById(R.id.spinner_sender_resolution);
|
||||
if (spinner == null) {
|
||||
return;
|
||||
}
|
||||
switch (mode(activity)) {
|
||||
case LEGACY:
|
||||
bindLegacy(activity, settings, spinner);
|
||||
break;
|
||||
case COMBINED:
|
||||
bindCombined(activity, settings, spinner);
|
||||
break;
|
||||
case SEPARATED:
|
||||
default:
|
||||
bindSeparated(activity, settings, spinner);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void readSenderResolution(AppCompatActivity activity, CastSettings settings) {
|
||||
Spinner spinner = activity.findViewById(R.id.spinner_sender_resolution);
|
||||
if (spinner != null) {
|
||||
CastSettingsBinder.readEnumSpinner(spinner, settings::setResolution);
|
||||
}
|
||||
}
|
||||
|
||||
/** Original / 720p / 480p / Auto. */
|
||||
private static void bindSeparated(AppCompatActivity activity, CastSettings settings, Spinner spinner) {
|
||||
CastSettings.Resolution[] modes = {
|
||||
CastSettings.Resolution.FULL,
|
||||
CastSettings.Resolution.HD_720P,
|
||||
CastSettings.Resolution.SD_480P,
|
||||
CastSettings.Resolution.ADAPTIVE,
|
||||
};
|
||||
String[] labels = {
|
||||
activity.getString(R.string.resolution_preset_original),
|
||||
activity.getString(R.string.resolution_preset_720p),
|
||||
activity.getString(R.string.resolution_preset_480p),
|
||||
activity.getString(R.string.resolution_preset_auto),
|
||||
};
|
||||
CastSettingsBinder.bindEnumSpinner(spinner, modes, labels, settings.getResolution(),
|
||||
settings::setResolution);
|
||||
}
|
||||
|
||||
/** Fixed 480p + fixed 720p + native original + bandwidth adaptive. */
|
||||
private static void bindCombined(AppCompatActivity activity, CastSettings settings, Spinner spinner) {
|
||||
CastSettings.Resolution[] modes = {
|
||||
CastSettings.Resolution.SD_480P,
|
||||
CastSettings.Resolution.HD_720P,
|
||||
CastSettings.Resolution.FULL,
|
||||
CastSettings.Resolution.ADAPTIVE,
|
||||
};
|
||||
String[] labels = {
|
||||
activity.getString(R.string.resolution_preset_480p),
|
||||
activity.getString(R.string.resolution_preset_720p),
|
||||
activity.getString(R.string.resolution_combined_original),
|
||||
activity.getString(R.string.resolution_combined_adaptive),
|
||||
};
|
||||
CastSettingsBinder.bindEnumSpinner(spinner, modes, labels, settings.getResolution(),
|
||||
settings::setResolution);
|
||||
}
|
||||
|
||||
/** Pre-refactor sender resolution list (subset previously in CastSettingsBinder). */
|
||||
private static void bindLegacy(AppCompatActivity activity, CastSettings settings, Spinner spinner) {
|
||||
CastSettings.Resolution[] modes = {
|
||||
CastSettings.Resolution.ADAPTIVE,
|
||||
CastSettings.Resolution.FIXED_1080P,
|
||||
CastSettings.Resolution.FIXED_720P,
|
||||
CastSettings.Resolution.FULL,
|
||||
CastSettings.Resolution.THREE_QUARTERS,
|
||||
CastSettings.Resolution.HALF,
|
||||
CastSettings.Resolution.HD_720P,
|
||||
};
|
||||
String[] labels = {
|
||||
activity.getString(R.string.resolution_adaptive),
|
||||
activity.getString(R.string.resolution_fixed_1080p),
|
||||
activity.getString(R.string.resolution_fixed_720p),
|
||||
activity.getString(R.string.resolution_full),
|
||||
activity.getString(R.string.resolution_75),
|
||||
activity.getString(R.string.resolution_half),
|
||||
activity.getString(R.string.resolution_720p),
|
||||
};
|
||||
CastSettingsBinder.bindEnumSpinner(spinner, modes, labels, settings.getResolution(),
|
||||
settings::setResolution);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.foxx.androidcast.sender;
|
||||
|
||||
/**
|
||||
* Developer-only: how the sender resolution spinner is presented.
|
||||
* <ul>
|
||||
* <li>{@link #SEPARATED} — Original / 720p / 480p / Auto (default)</li>
|
||||
* <li>{@link #LEGACY} — full pre-refactor preset list</li>
|
||||
* <li>{@link #COMBINED} — fixed 480p + fixed 720p + full original + BW adaptive</li>
|
||||
* </ul>
|
||||
*/
|
||||
public enum SenderResolutionUiMode {
|
||||
SEPARATED,
|
||||
LEGACY,
|
||||
COMBINED
|
||||
}
|
||||
@@ -45,6 +45,34 @@
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/dev_receiver_adaptive_display" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="28dp"
|
||||
android:text="@string/dev_sender_resolution_ui_section"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/dev_sender_resolution_ui_mode"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinner_dev_sender_resolution_ui_mode"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/dev_sender_resolution_ui_hint"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -46,6 +46,12 @@
|
||||
<string name="resolution_fixed_1080p">1080p (1920×1080)</string>
|
||||
<string name="resolution_fixed_720p">720p (1280×720)</string>
|
||||
<string name="resolution_adaptive">Адаптивный (предсказание, в разработке)</string>
|
||||
<string name="resolution_preset_original">Оригинал</string>
|
||||
<string name="resolution_preset_720p">720p</string>
|
||||
<string name="resolution_preset_480p">480p</string>
|
||||
<string name="resolution_preset_auto">Авто</string>
|
||||
<string name="resolution_combined_original">Оригинал (нативное)</string>
|
||||
<string name="resolution_combined_adaptive">Адаптивный (оценка канала)</string>
|
||||
<string name="label_video_codec">Видео кодек</string>
|
||||
<string name="label_adjustment_preset">Тонкие быстрые настройки</string>
|
||||
<string name="label_bitrate_mode">Bitrate</string>
|
||||
@@ -188,6 +194,12 @@
|
||||
<string name="licenses_title">Лицензии</string>
|
||||
<string name="licenses_load_error">Не могу найти факл лицензий</string>
|
||||
<string name="developer_settings_title">Для разработчиков</string>
|
||||
<string name="dev_sender_resolution_ui_section">Разрешение отправителя (тест)</string>
|
||||
<string name="dev_sender_resolution_ui_mode">Режим списка разрешений</string>
|
||||
<string name="dev_sender_resolution_ui_separated">Раздельный (Оригинал / 720p / 480p / Авто)</string>
|
||||
<string name="dev_sender_resolution_ui_legacy">Старый (полный список)</string>
|
||||
<string name="dev_sender_resolution_ui_combined">Комбинированный (480p + 720p + оригинал + адаптив)</string>
|
||||
<string name="dev_sender_resolution_ui_hint">Меняет список разрешений в настройках трансляции. По умолчанию: Раздельный. Откройте настройки cast заново.</string>
|
||||
<string name="dev_wired_display_section">USB-C / HDMI (план D)</string>
|
||||
<string name="dev_commercial_section">Монетизация (выкл. по умолчанию)</string>
|
||||
<string name="transport_usb_tether">USB-тетер (эксперимент)</string>
|
||||
|
||||
@@ -46,6 +46,12 @@
|
||||
<string name="resolution_fixed_1080p">Fixed 1080p (fit in 1920×1080)</string>
|
||||
<string name="resolution_fixed_720p">Fixed 720p (fit in 1280×720)</string>
|
||||
<string name="resolution_adaptive">Adaptive (BW estimator, preview)</string>
|
||||
<string name="resolution_preset_original">Original</string>
|
||||
<string name="resolution_preset_720p">720p</string>
|
||||
<string name="resolution_preset_480p">480p</string>
|
||||
<string name="resolution_preset_auto">Auto</string>
|
||||
<string name="resolution_combined_original">Original (native)</string>
|
||||
<string name="resolution_combined_adaptive">Adaptive (BW estimator)</string>
|
||||
<string name="label_video_codec">Video codec</string>
|
||||
<string name="label_adjustment_preset">Cast adjustment preset</string>
|
||||
<string name="label_bitrate_mode">Bitrate</string>
|
||||
@@ -199,6 +205,12 @@
|
||||
<string name="licenses_title">Licenses</string>
|
||||
<string name="licenses_load_error">Could not load license text.</string>
|
||||
<string name="developer_settings_title">Developer settings</string>
|
||||
<string name="dev_sender_resolution_ui_section">Sender resolution UI (test)</string>
|
||||
<string name="dev_sender_resolution_ui_mode">Resolution preset mode</string>
|
||||
<string name="dev_sender_resolution_ui_separated">Separated (Original / 720p / 480p / Auto)</string>
|
||||
<string name="dev_sender_resolution_ui_legacy">Legacy (full preset list)</string>
|
||||
<string name="dev_sender_resolution_ui_combined">Combined (480p + 720p + original + adaptive)</string>
|
||||
<string name="dev_sender_resolution_ui_hint">Changes the sender resolution spinner in cast settings. Default: Separated. Re-open cast settings to refresh labels.</string>
|
||||
<string name="dev_wired_display_section">USB-C / HDMI (roadmap D)</string>
|
||||
<string name="dev_wired_display_hints">Show sender status when external display is plugged in</string>
|
||||
<string name="dev_wired_display_hint">Wired HDMI uses the OS display stack, not the cast protocol. If no second screen appears in system Display settings, adapters cannot be fixed in-app. See docs/USB_HDMI_CAST.md.</string>
|
||||
|
||||
@@ -11,6 +11,8 @@ package com.foxx.androidcast;
|
||||
* - Cursor Agent (project assistant)
|
||||
* Digest: SHA256 6839d31665eec94c6c24a1a54c86dfaaef483d19196fe6b6a754eb7156c09b26
|
||||
**********************************************************************/
|
||||
import android.util.DisplayMetrics;
|
||||
|
||||
import com.foxx.androidcast.sender.calibration.CalibrationSourceDimensions;
|
||||
|
||||
import org.junit.Test;
|
||||
@@ -27,4 +29,18 @@ public class CastResolutionTest {
|
||||
assertEquals(400, size.height);
|
||||
assertTrue(size.width > size.height);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sd480pCapsLongEdge() {
|
||||
CastSettings settings = new CastSettings();
|
||||
settings.setResolution(CastSettings.Resolution.SD_480P);
|
||||
DisplayMetrics metrics = new DisplayMetrics();
|
||||
metrics.widthPixels = 2000;
|
||||
metrics.heightPixels = 1200;
|
||||
metrics.densityDpi = 320;
|
||||
CastTuningEngine.CaptureLimits limits = CastTuningEngine.resolveCaptureLimits(settings, null);
|
||||
assertEquals(480, limits.maxHeight);
|
||||
CastResolution.Size size = CastResolution.compute(metrics, settings, null);
|
||||
assertEquals(480, Math.max(size.width, size.height));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user