feat(chromecast): send custom message for overlay subtitles

This commit is contained in:
2026-07-21 19:28:36 +02:00
parent e6a06b13a4
commit a335509b4e
15 changed files with 366 additions and 241 deletions
+139 -99
View File
@@ -1,129 +1,169 @@
import { AndroidConfig, withAndroidManifest, withMainActivity, type ConfigPlugin } from '@expo/config-plugins';
import { addImports, appendContentsInsideDeclarationBlock } from '@expo/config-plugins/build/android/codeMod';
import {
AndroidConfig,
type ConfigPlugin,
withAndroidManifest,
withMainActivity,
} from "@expo/config-plugins";
import {
addImports,
appendContentsInsideDeclarationBlock,
} from "@expo/config-plugins/build/android/codeMod";
const MODE_MARKER = 'OmniView.onActivityPipModeChanged(';
const UI_STATE_MARKER = 'OmniView.onActivityPipTransitionToPip(';
const MODE_MARKER = "OmniView.onActivityPipModeChanged(";
const UI_STATE_MARKER = "OmniView.onActivityPipTransitionToPip(";
function patchMainActivityForPip(mainActivity: string, language: 'java' | 'kt'): string {
const isJava = language === 'java';
function patchMainActivityForPip(
mainActivity: string,
language: "java" | "kt",
): string {
const isJava = language === "java";
if (mainActivity.includes(MODE_MARKER) && mainActivity.includes(UI_STATE_MARKER)) {
return mainActivity;
}
if (
mainActivity.includes(MODE_MARKER) &&
mainActivity.includes(UI_STATE_MARKER)
) {
return mainActivity;
}
const withRequiredImports = addImports(
mainActivity,
['android.app.PictureInPictureUiState', 'android.os.Build', 'dev.zoriya.omni.OmniView'],
isJava
);
const withRequiredImports = addImports(
mainActivity,
[
"android.app.PictureInPictureUiState",
"android.os.Build",
"dev.zoriya.omni.OmniView",
],
isJava,
);
let output = withRequiredImports;
let output = withRequiredImports;
if (!output.includes(MODE_MARKER)) {
const modeChangedBlock = isJava
? [
'\n @Override',
' public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {',
' super.onPictureInPictureModeChanged(isInPictureInPictureMode);',
' OmniView.onActivityPipModeChanged(this, isInPictureInPictureMode);',
' }\n',
]
: [
'\n override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {',
' super.onPictureInPictureModeChanged(isInPictureInPictureMode)',
' OmniView.onActivityPipModeChanged(this, isInPictureInPictureMode)',
' }\n',
];
if (!output.includes(MODE_MARKER)) {
const modeChangedBlock = isJava
? [
"\n @Override",
" public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {",
" super.onPictureInPictureModeChanged(isInPictureInPictureMode);",
" OmniView.onActivityPipModeChanged(this, isInPictureInPictureMode);",
" }\n",
]
: [
"\n override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {",
" super.onPictureInPictureModeChanged(isInPictureInPictureMode)",
" OmniView.onActivityPipModeChanged(this, isInPictureInPictureMode)",
" }\n",
];
output = appendContentsInsideDeclarationBlock(output, 'class MainActivity', modeChangedBlock.join('\n'));
}
output = appendContentsInsideDeclarationBlock(
output,
"class MainActivity",
modeChangedBlock.join("\n"),
);
}
if (!output.includes(UI_STATE_MARKER)) {
const uiStateChangedBlock = isJava
? [
'\n @Override',
' public void onPictureInPictureUiStateChanged(PictureInPictureUiState pipState) {',
' super.onPictureInPictureUiStateChanged(pipState);',
' if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && pipState.isTransitioningToPip()) {',
' OmniView.onActivityPipTransitionToPip(this);',
' }',
' }\n',
]
: [
'\n override fun onPictureInPictureUiStateChanged(pipState: PictureInPictureUiState) {',
' super.onPictureInPictureUiStateChanged(pipState)',
' if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && pipState.isTransitioningToPip) {',
' OmniView.onActivityPipTransitionToPip(this)',
' }',
' }\n',
];
if (!output.includes(UI_STATE_MARKER)) {
const uiStateChangedBlock = isJava
? [
"\n @Override",
" public void onPictureInPictureUiStateChanged(PictureInPictureUiState pipState) {",
" super.onPictureInPictureUiStateChanged(pipState);",
" if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && pipState.isTransitioningToPip()) {",
" OmniView.onActivityPipTransitionToPip(this);",
" }",
" }\n",
]
: [
"\n override fun onPictureInPictureUiStateChanged(pipState: PictureInPictureUiState) {",
" super.onPictureInPictureUiStateChanged(pipState)",
" if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && pipState.isTransitioningToPip) {",
" OmniView.onActivityPipTransitionToPip(this)",
" }",
" }\n",
];
output = appendContentsInsideDeclarationBlock(output, 'class MainActivity', uiStateChangedBlock.join('\n'));
}
output = appendContentsInsideDeclarationBlock(
output,
"class MainActivity",
uiStateChangedBlock.join("\n"),
);
}
return output;
return output;
}
function ensureUsesFeature(
androidManifest: AndroidConfig.Manifest.AndroidManifest,
featureName: string,
required: 'true' | 'false'
androidManifest: AndroidConfig.Manifest.AndroidManifest,
featureName: string,
required: "true" | "false",
): void {
const usesFeature = androidManifest.manifest['uses-feature'] ?? [];
const existing = usesFeature.find((feature) => feature.$['android:name'] === featureName);
const usesFeature = androidManifest.manifest["uses-feature"] ?? [];
const existing = usesFeature.find(
(feature) => feature.$["android:name"] === featureName,
);
if (existing) {
existing.$['android:required'] = required;
} else {
usesFeature.push({
$: {
'android:name': featureName,
'android:required': required,
},
});
}
if (existing) {
existing.$["android:required"] = required;
} else {
usesFeature.push({
$: {
"android:name": featureName,
"android:required": required,
},
});
}
androidManifest.manifest['uses-feature'] = usesFeature;
androidManifest.manifest["uses-feature"] = usesFeature;
}
function ensureConfigChanges(mainActivity: AndroidConfig.Manifest.ManifestActivity): void {
const requiredChanges = ['screenSize', 'smallestScreenSize', 'screenLayout', 'orientation'];
const existing = mainActivity.$['android:configChanges'] ?? '';
const existingSet = new Set(
existing
.split('|')
.map((item) => item.trim())
.filter(Boolean)
);
function ensureConfigChanges(
mainActivity: AndroidConfig.Manifest.ManifestActivity,
): void {
const requiredChanges = [
"screenSize",
"smallestScreenSize",
"screenLayout",
"orientation",
];
const existing = mainActivity.$["android:configChanges"] ?? "";
const existingSet = new Set(
existing
.split("|")
.map((item) => item.trim())
.filter(Boolean),
);
requiredChanges.forEach((item) => existingSet.add(item));
mainActivity.$['android:configChanges'] = Array.from(existingSet).join('|');
requiredChanges.forEach((item) => existingSet.add(item));
mainActivity.$["android:configChanges"] = Array.from(existingSet).join("|");
}
function patchManifestForPip(
androidManifest: AndroidConfig.Manifest.AndroidManifest
androidManifest: AndroidConfig.Manifest.AndroidManifest,
): AndroidConfig.Manifest.AndroidManifest {
ensureUsesFeature(androidManifest, 'android.software.picture_in_picture', 'false');
ensureUsesFeature(
androidManifest,
"android.software.picture_in_picture",
"false",
);
const mainActivity = AndroidConfig.Manifest.getMainActivityOrThrow(androidManifest);
mainActivity.$['android:supportsPictureInPicture'] = 'true';
mainActivity.$['android:resizeableActivity'] = 'true';
ensureConfigChanges(mainActivity);
const mainActivity =
AndroidConfig.Manifest.getMainActivityOrThrow(androidManifest);
mainActivity.$["android:supportsPictureInPicture"] = "true";
mainActivity.$["android:resizeableActivity"] = "true";
ensureConfigChanges(mainActivity);
return androidManifest;
return androidManifest;
}
export const withPip: ConfigPlugin = (config) => {
const withMainActivityPatched = withMainActivity(config, (activityConfig) => {
activityConfig.modResults.contents = patchMainActivityForPip(
activityConfig.modResults.contents,
activityConfig.modResults.language
);
return activityConfig;
});
const withMainActivityPatched = withMainActivity(config, (activityConfig) => {
activityConfig.modResults.contents = patchMainActivityForPip(
activityConfig.modResults.contents,
activityConfig.modResults.language,
);
return activityConfig;
});
return withAndroidManifest(withMainActivityPatched, (manifestConfig) => {
manifestConfig.modResults = patchManifestForPip(manifestConfig.modResults);
return manifestConfig;
});
return withAndroidManifest(withMainActivityPatched, (manifestConfig) => {
manifestConfig.modResults = patchManifestForPip(manifestConfig.modResults);
return manifestConfig;
});
};