mirror of
https://github.com/zoriya/react-native-omni.git
synced 2026-08-05 05:36:17 +00:00
feat(chromecast): send custom message for overlay subtitles
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
||||
module.exports = require('./plugin/build').default;
|
||||
module.exports = require("./plugin/build").default;
|
||||
|
||||
+2
-2
@@ -88,7 +88,7 @@ function PlayerExample({
|
||||
|
||||
useEffect(() => {
|
||||
refreshTracks();
|
||||
}, [refreshTracks, trackLabel]);
|
||||
}, [refreshTracks]);
|
||||
|
||||
const pushLog = useCallback((message: string) => {
|
||||
setLogs((prev) => {
|
||||
@@ -194,7 +194,7 @@ function PlayerExample({
|
||||
|
||||
const cyclePlaybackRate = () => {
|
||||
const rates = [0.75, 1, 1.25, 1.5, 2];
|
||||
const index = rates.findIndex((rate) => rate === playbackRate);
|
||||
const index = rates.indexOf(playbackRate);
|
||||
const nextIndex = (index + 1) % rates.length;
|
||||
player.playbackRate = rates[nextIndex];
|
||||
};
|
||||
|
||||
@@ -15,7 +15,11 @@ const config = {
|
||||
...(useWeb && {
|
||||
resolver: {
|
||||
extraNodeModules: {
|
||||
"react-native": path.resolve(__dirname, "node_modules", "react-native-web"),
|
||||
"react-native": path.resolve(
|
||||
__dirname,
|
||||
"node_modules",
|
||||
"react-native-web",
|
||||
),
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const path = require("path");
|
||||
const path = require("node:path");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
|
||||
const appDirectory = path.resolve(__dirname);
|
||||
|
||||
+6
-6
@@ -1,8 +1,8 @@
|
||||
import { createRunOncePlugin } from '@expo/config-plugins';
|
||||
import type { ConfigPlugin } from '@expo/config-plugins';
|
||||
import pkg from '../../package.json';
|
||||
import { withMediaNotifications } from './withMediaNotifications';
|
||||
import { withPip } from './withPip';
|
||||
import type { ConfigPlugin } from "@expo/config-plugins";
|
||||
import { createRunOncePlugin } from "@expo/config-plugins";
|
||||
import pkg from "../../package.json";
|
||||
import { withMediaNotifications } from "./withMediaNotifications";
|
||||
import { withPip } from "./withPip";
|
||||
|
||||
const withOmni: ConfigPlugin = (config) => {
|
||||
let nextConfig = withPip(config);
|
||||
@@ -12,4 +12,4 @@ const withOmni: ConfigPlugin = (config) => {
|
||||
|
||||
export default createRunOncePlugin(withOmni, pkg.name, pkg.version);
|
||||
|
||||
export { withPip, withMediaNotifications };
|
||||
export { withMediaNotifications, withPip };
|
||||
|
||||
@@ -1,51 +1,60 @@
|
||||
import { AndroidConfig, withAndroidManifest, type ConfigPlugin } from '@expo/config-plugins';
|
||||
import {
|
||||
AndroidConfig,
|
||||
type ConfigPlugin,
|
||||
withAndroidManifest,
|
||||
} from "@expo/config-plugins";
|
||||
|
||||
function ensureUsesPermission(
|
||||
androidManifest: AndroidConfig.Manifest.AndroidManifest,
|
||||
permission: string
|
||||
permission: string,
|
||||
): void {
|
||||
const usesPermissions = androidManifest.manifest['uses-permission'] ?? [];
|
||||
const alreadyDefined = usesPermissions.some((item) => item.$['android:name'] === permission);
|
||||
const usesPermissions = androidManifest.manifest["uses-permission"] ?? [];
|
||||
const alreadyDefined = usesPermissions.some(
|
||||
(item) => item.$["android:name"] === permission,
|
||||
);
|
||||
|
||||
if (!alreadyDefined) {
|
||||
usesPermissions.push({
|
||||
$: {
|
||||
'android:name': permission,
|
||||
"android:name": permission,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
androidManifest.manifest['uses-permission'] = usesPermissions;
|
||||
androidManifest.manifest["uses-permission"] = usesPermissions;
|
||||
}
|
||||
|
||||
function patchManifestForMediaNotifications(
|
||||
androidManifest: AndroidConfig.Manifest.AndroidManifest
|
||||
androidManifest: AndroidConfig.Manifest.AndroidManifest,
|
||||
): AndroidConfig.Manifest.AndroidManifest {
|
||||
const mainApplication = AndroidConfig.Manifest.getMainApplication(androidManifest);
|
||||
const mainApplication =
|
||||
AndroidConfig.Manifest.getMainApplication(androidManifest);
|
||||
if (!mainApplication) {
|
||||
console.warn(
|
||||
'AndroidManifest.xml is missing a <application android:name=".MainApplication" /> element - skipping Omni media service config.'
|
||||
'AndroidManifest.xml is missing a <application android:name=".MainApplication" /> element - skipping Omni media service config.',
|
||||
);
|
||||
return androidManifest;
|
||||
}
|
||||
|
||||
const services = mainApplication.service ?? [];
|
||||
const serviceName = 'dev.zoriya.omni.OmniPlayerService';
|
||||
const hasService = services.some((service) => service.$['android:name'] === serviceName);
|
||||
const serviceName = "dev.zoriya.omni.OmniPlayerService";
|
||||
const hasService = services.some(
|
||||
(service) => service.$["android:name"] === serviceName,
|
||||
);
|
||||
if (!hasService) {
|
||||
services.push({
|
||||
$: {
|
||||
'android:name': serviceName,
|
||||
'android:enabled': 'true',
|
||||
'android:exported': 'true',
|
||||
'android:foregroundServiceType': 'mediaPlayback',
|
||||
"android:name": serviceName,
|
||||
"android:enabled": "true",
|
||||
"android:exported": "true",
|
||||
"android:foregroundServiceType": "mediaPlayback",
|
||||
},
|
||||
'intent-filter': [
|
||||
"intent-filter": [
|
||||
{
|
||||
action: [
|
||||
{
|
||||
$: {
|
||||
'android:name': 'androidx.media3.session.MediaSessionService',
|
||||
"android:name": "androidx.media3.session.MediaSessionService",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -55,14 +64,22 @@ function patchManifestForMediaNotifications(
|
||||
}
|
||||
mainApplication.service = services;
|
||||
|
||||
ensureUsesPermission(androidManifest, 'android.permission.FOREGROUND_SERVICE');
|
||||
ensureUsesPermission(androidManifest, 'android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK');
|
||||
ensureUsesPermission(
|
||||
androidManifest,
|
||||
"android.permission.FOREGROUND_SERVICE",
|
||||
);
|
||||
ensureUsesPermission(
|
||||
androidManifest,
|
||||
"android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK",
|
||||
);
|
||||
return androidManifest;
|
||||
}
|
||||
|
||||
export const withMediaNotifications: ConfigPlugin = (config) => {
|
||||
return withAndroidManifest(config, (manifestConfig) => {
|
||||
manifestConfig.modResults = patchManifestForMediaNotifications(manifestConfig.modResults);
|
||||
manifestConfig.modResults = patchManifestForMediaNotifications(
|
||||
manifestConfig.modResults,
|
||||
);
|
||||
return manifestConfig;
|
||||
});
|
||||
};
|
||||
|
||||
+92
-52
@@ -1,20 +1,38 @@
|
||||
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)) {
|
||||
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
|
||||
[
|
||||
"android.app.PictureInPictureUiState",
|
||||
"android.os.Build",
|
||||
"dev.zoriya.omni.OmniView",
|
||||
],
|
||||
isJava,
|
||||
);
|
||||
|
||||
let output = withRequiredImports;
|
||||
@@ -22,43 +40,51 @@ function patchMainActivityForPip(mainActivity: string, language: 'java' | 'kt'):
|
||||
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",
|
||||
" 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',
|
||||
"\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",
|
||||
" 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',
|
||||
"\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;
|
||||
@@ -67,47 +93,61 @@ function patchMainActivityForPip(mainActivity: string, language: 'java' | 'kt'):
|
||||
function ensureUsesFeature(
|
||||
androidManifest: AndroidConfig.Manifest.AndroidManifest,
|
||||
featureName: string,
|
||||
required: 'true' | 'false'
|
||||
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;
|
||||
existing.$["android:required"] = required;
|
||||
} else {
|
||||
usesFeature.push({
|
||||
$: {
|
||||
'android:name': featureName,
|
||||
'android:required': required,
|
||||
"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'] ?? '';
|
||||
function ensureConfigChanges(
|
||||
mainActivity: AndroidConfig.Manifest.ManifestActivity,
|
||||
): void {
|
||||
const requiredChanges = [
|
||||
"screenSize",
|
||||
"smallestScreenSize",
|
||||
"screenLayout",
|
||||
"orientation",
|
||||
];
|
||||
const existing = mainActivity.$["android:configChanges"] ?? "";
|
||||
const existingSet = new Set(
|
||||
existing
|
||||
.split('|')
|
||||
.split("|")
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean)
|
||||
.filter(Boolean),
|
||||
);
|
||||
|
||||
requiredChanges.forEach((item) => existingSet.add(item));
|
||||
mainActivity.$['android:configChanges'] = Array.from(existingSet).join('|');
|
||||
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';
|
||||
const mainActivity =
|
||||
AndroidConfig.Manifest.getMainActivityOrThrow(androidManifest);
|
||||
mainActivity.$["android:supportsPictureInPicture"] = "true";
|
||||
mainActivity.$["android:resizeableActivity"] = "true";
|
||||
ensureConfigChanges(mainActivity);
|
||||
|
||||
return androidManifest;
|
||||
@@ -117,7 +157,7 @@ export const withPip: ConfigPlugin = (config) => {
|
||||
const withMainActivityPatched = withMainActivity(config, (activityConfig) => {
|
||||
activityConfig.modResults.contents = patchMainActivityForPip(
|
||||
activityConfig.modResults.contents,
|
||||
activityConfig.modResults.language
|
||||
activityConfig.modResults.language,
|
||||
);
|
||||
return activityConfig;
|
||||
});
|
||||
|
||||
+8
-2
@@ -20,7 +20,10 @@ const updateViewManagerFiles = async (file) => {
|
||||
const viewManagerStr = await readFile(viewManagerFile, { encoding: "utf8" });
|
||||
await writeFile(
|
||||
viewManagerFile,
|
||||
viewManagerStr.replace(/com\.margelo\.nitro\.omni\.\*/g, "dev.zoriya.omni.*"),
|
||||
viewManagerStr.replace(
|
||||
/com\.margelo\.nitro\.omni\.\*/g,
|
||||
"dev.zoriya.omni.*",
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -49,6 +52,9 @@ const androidWorkaround = async () => {
|
||||
}
|
||||
|
||||
const str = await readFile(androidOnLoadFile, { encoding: "utf8" });
|
||||
await writeFile(androidOnLoadFile, str.replace(/com\/margelo\/nitro\//g, "dev/zoriya/"));
|
||||
await writeFile(
|
||||
androidOnLoadFile,
|
||||
str.replace(/com\/margelo\/nitro\//g, "dev/zoriya/"),
|
||||
);
|
||||
};
|
||||
androidWorkaround();
|
||||
|
||||
+22
-1
@@ -126,6 +126,27 @@ export const useEvent = <Event extends keyof OmniEvents>(
|
||||
}
|
||||
return undefined;
|
||||
}, [event, player]);
|
||||
|
||||
// ASS/PGS (overlay) subtitle selection is not a videojs text-track change, so
|
||||
// re-fire subtitleChange when the overlay subtitle changes too — consumers
|
||||
// only need useEvent("subtitleChange").
|
||||
useEffect(() => {
|
||||
if (event !== "subtitleChange") return;
|
||||
return player.subscribeOverlaySubtitle(() => {
|
||||
const sel = player.subtitles.find((s) => s.selected);
|
||||
const cb = callbackRef.current as (s: unknown) => void;
|
||||
cb(
|
||||
sel
|
||||
? {
|
||||
id: sel.id,
|
||||
label: sel.label,
|
||||
language: sel.language,
|
||||
selected: true,
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
});
|
||||
}, [event, player]);
|
||||
};
|
||||
|
||||
function createMapper<Key extends keyof OmniPlayerState, State, Result>(
|
||||
@@ -182,7 +203,7 @@ export const stateMapper = {
|
||||
if (!r) return "unavailable";
|
||||
if (r.remotePlaybackState === "connecting") return "connecting";
|
||||
if (r.remotePlaybackState === "connected") return "connected";
|
||||
return r.remotePlaybackAvailability
|
||||
return r.remotePlaybackAvailability;
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -242,6 +242,26 @@ export class WebOmniPlayer implements OmniPlayer {
|
||||
const tracks = selectTextTrack(this._store.state);
|
||||
tracks?.selectSubtitlesTrack(overlay || !subtitle ? "off" : subtitle.id);
|
||||
this.setOverlaySubtitle(overlay ?? null);
|
||||
|
||||
if (this.castStatus === "connected") {
|
||||
try {
|
||||
// if the cast receiver handles overlay subtitles like us, send this message.
|
||||
const ctx = (
|
||||
window as unknown as {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: cast sender SDK global
|
||||
cast?: { framework?: { CastContext?: { getInstance(): any } } };
|
||||
}
|
||||
).cast?.framework?.CastContext?.getInstance?.();
|
||||
ctx
|
||||
?.getCurrentSession?.()
|
||||
?.sendMessage("urn:x-cast:dev.zoriya.omni", {
|
||||
subtitle: overlay?.id ?? null,
|
||||
})
|
||||
?.catch?.(() => {});
|
||||
} catch {
|
||||
// no active cast session / sender SDK not loaded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private setOverlaySubtitle(sub: Subtitle | null): void {
|
||||
|
||||
@@ -7,6 +7,8 @@ export interface Source {
|
||||
fonts?: string[];
|
||||
metadata?: Metadata;
|
||||
mixAudio?: MixAudioMode;
|
||||
// contentId of a cast request. defaults to src but can be overridden here.
|
||||
castId?: string;
|
||||
// Opaque, consumer-defined payload forwarded verbatim to the cast receiver
|
||||
// as the load request's customData. omni does not interpret it.
|
||||
castData?: Record<string, string>;
|
||||
|
||||
+21
-6
@@ -9,6 +9,7 @@ import {
|
||||
useRef,
|
||||
useSyncExternalStore,
|
||||
} from "react";
|
||||
import { usePlayerState } from "./events";
|
||||
import {
|
||||
getSubtitleFormat,
|
||||
isCustomSubtitle,
|
||||
@@ -58,7 +59,8 @@ const SubtitleOverlay = ({
|
||||
const renditions = player.rendition;
|
||||
const videoWidth = Math.max(0, ...renditions.map((r) => r.width));
|
||||
const videoHeight = Math.max(0, ...renditions.map((r) => r.height));
|
||||
import("jassub").then(({ default: JASSUB }) => {
|
||||
import("jassub")
|
||||
.then(({ default: JASSUB }) => {
|
||||
const instance = new JASSUB({
|
||||
video: el,
|
||||
subUrl: subtitle.link,
|
||||
@@ -67,17 +69,21 @@ const SubtitleOverlay = ({
|
||||
...(fonts?.length && { fonts }),
|
||||
...(jassub?.workerUrl && { workerUrl: jassub.workerUrl }),
|
||||
...(jassub?.wasmUrl && { wasmUrl: jassub.wasmUrl }),
|
||||
...(jassub?.modernWasmUrl && { modernWasmUrl: jassub.modernWasmUrl }),
|
||||
...(jassub?.modernWasmUrl && {
|
||||
modernWasmUrl: jassub.modernWasmUrl,
|
||||
}),
|
||||
...(jassub?.fontUrl && {
|
||||
availableFonts: { "liberation sans": jassub.fontUrl },
|
||||
defaultFont: "liberation sans",
|
||||
}),
|
||||
});
|
||||
attach({ destroy: () => instance.destroy() });
|
||||
});
|
||||
})
|
||||
.catch((e) => console.error("[omni] failed to render ass subtitle", e));
|
||||
} else {
|
||||
const pgs = assetsRef.current?.pgs;
|
||||
import("libpgs").then(({ PgsRenderer }) => {
|
||||
import("libpgs")
|
||||
.then(({ PgsRenderer }) => {
|
||||
const instance = new PgsRenderer({
|
||||
video: el,
|
||||
subUrl: subtitle.link,
|
||||
@@ -86,7 +92,8 @@ const SubtitleOverlay = ({
|
||||
new URL("libpgs/dist/libpgs.worker.js", import.meta.url).href,
|
||||
});
|
||||
attach({ destroy: () => instance.dispose() });
|
||||
});
|
||||
})
|
||||
.catch((e) => console.error("[omni] failed to render pgs subtitle", e));
|
||||
}
|
||||
|
||||
return () => {
|
||||
@@ -116,6 +123,7 @@ export const OmniView = ({
|
||||
|
||||
const headersRef = useRef(src?.headers);
|
||||
headersRef.current = src?.headers;
|
||||
const castId = player.source?.castId;
|
||||
const castData = player.source?.castData;
|
||||
|
||||
const config = useMemo<HlsMediaConfig>(
|
||||
@@ -132,11 +140,16 @@ export const OmniView = ({
|
||||
googleCast: {
|
||||
receiver: player.castOptions?.receiverApplicationId,
|
||||
customData: castData ?? null,
|
||||
...(castId && { src: castId }),
|
||||
},
|
||||
}),
|
||||
[player.castOptions, castData],
|
||||
[player.castOptions, castData, castId],
|
||||
);
|
||||
|
||||
// While casting, the receiver renders subtitles (the player forwards the
|
||||
// selection); skip rendering them locally on the idle <video>.
|
||||
const castStatus = usePlayerState("castStatus");
|
||||
|
||||
return (
|
||||
<VideoPlayer.Container
|
||||
ref={containerRef}
|
||||
@@ -166,11 +179,13 @@ export const OmniView = ({
|
||||
))}
|
||||
</Tech>
|
||||
)}
|
||||
{castStatus !== "connected" && castStatus !== "connecting" && (
|
||||
<SubtitleOverlay
|
||||
video={ref}
|
||||
assets={subtitleAssets}
|
||||
fonts={player.source?.fonts}
|
||||
/>
|
||||
)}
|
||||
</VideoPlayer.Container>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user