Compare commits

..
6 Commits
Author SHA1 Message Date
zoriya 0d8b33aee3 fix(event): fix stale callbacks 2026-07-16 13:58:32 +02:00
zoriya 645b226359 fix(source): ensure object update doesn't reset player 2026-07-16 12:27:50 +02:00
zoriya 61ee6238d5 fix: oups i need to use sementic commits 2026-07-14 17:15:48 +02:00
zoriya a7717dfcb9 Fix native track ids 2026-07-14 17:12:31 +02:00
zoriya 337f338224 Fix hls tech detection 2026-07-14 16:20:04 +02:00
zoriya 72328aacf6 fix: peer dependencies are now loose 2026-07-14 15:04:31 +02:00
7 changed files with 46 additions and 36 deletions
+2 -6
View File
@@ -43,16 +43,12 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-bun- ${{ runner.os }}-bun-
- name: Install npm dependencies (bun) - name: Install/Typecheck/Build
# We use a `prepare` script that run builds to support git installs.
run: bun install run: bun install
- name: Build lib
run: bun run build
- name: Release - name: Release
env: env:
# No NPM_TOKEN: auth happens via OIDC trusted publishing (id-token: write above).
# Provenance is generated automatically, so NPM_CONFIG_PROVENANCE is not needed.
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GIT_AUTHOR_NAME: ${{ github.actor }} GIT_AUTHOR_NAME: ${{ github.actor }}
GIT_AUTHOR_EMAIL: '${{ github.actor }}@users.noreply.github.com' GIT_AUTHOR_EMAIL: '${{ github.actor }}@users.noreply.github.com'
+1 -9
View File
@@ -1,4 +1,4 @@
# omni # react-native-omni
A library to have real players on android and web. It uses VLC on android and A library to have real players on android and web. It uses VLC on android and
videojs v10 on the web (ios not implemented yet, PR welcome) videojs v10 on the web (ios not implemented yet, PR welcome)
@@ -53,14 +53,6 @@ subtitles rendering:
<summary>ass rendering</summary> <summary>ass rendering</summary>
#### Fonts
JASSUB only renders glyphs for fonts it has, and does **not** ship a usable
default font. Provide the fonts a subtitle references through
`source.fonts` (an array of font-file URLs). Optionally set
`subtitleAssets.jassub.fontUrl` as a fallback for styles whose font isn't
listed. If no matching font is available, that text simply won't appear.
#### Cross-origin isolation #### Cross-origin isolation
jassub's wasm is multi-threaded and relies on `SharedArrayBuffer`, which jassub's wasm is multi-threaded and relies on `SharedArrayBuffer`, which
+3 -3
View File
@@ -23,9 +23,9 @@
"typescript": "^7.0.2", "typescript": "^7.0.2",
}, },
"peerDependencies": { "peerDependencies": {
"react": "19.2.7", "react": ">=19.0.0",
"react-native": "0.86.0", "react-native": ">=0.86.0",
"react-native-nitro-modules": "0.36.1", "react-native-nitro-modules": ">=0.36.0",
}, },
}, },
"example": { "example": {
+8 -4
View File
@@ -14,6 +14,7 @@
"typecheck": "tsc --noEmit && tsc -p plugin/tsconfig.json --noEmit", "typecheck": "tsc --noEmit && tsc -p plugin/tsconfig.json --noEmit",
"build:plugin": "tsc -p plugin/tsconfig.json", "build:plugin": "tsc -p plugin/tsconfig.json",
"build": "bun run typecheck && bob build && bun run build:plugin", "build": "bun run typecheck && bob build && bun run build:plugin",
"prepare": "bun run build",
"codegen": "nitrogen --logLevel=\"debug\" && node post-script.js", "codegen": "nitrogen --logLevel=\"debug\" && node post-script.js",
"release": "semantic-release" "release": "semantic-release"
}, },
@@ -48,7 +49,10 @@
"workspaces": [ "workspaces": [
"example" "example"
], ],
"repository": "https://github.com/zoriya/react-native-omni.git", "repository": {
"type": "git",
"url": "git+https://github.com/zoriya/react-native-omni.git"
},
"author": "Zoe Roux", "author": "Zoe Roux",
"license": "MIT", "license": "MIT",
"bugs": "https://github.com/zoriya/react-native-omni/issues", "bugs": "https://github.com/zoriya/react-native-omni/issues",
@@ -76,9 +80,9 @@
"typescript": "^7.0.2" "typescript": "^7.0.2"
}, },
"peerDependencies": { "peerDependencies": {
"react": "19.2.7", "react": ">=19.0.0",
"react-native": "0.86.0", "react-native": ">=0.86.0",
"react-native-nitro-modules": "0.36.1" "react-native-nitro-modules": ">=0.36.0"
}, },
"react-native-builder-bob": { "react-native-builder-bob": {
"source": "src", "source": "src",
+2 -4
View File
@@ -108,18 +108,16 @@ export const useEvent = <Event extends keyof OmniEvents>(
prevRef.current = value; prevRef.current = value;
}, [value, config]); }, [value, config]);
// prev/next are triggered by the app (or the media session) rather than the
// media element
useEffect(() => { useEffect(() => {
// don't use callbackRef.current directly to keep future callback updates
const cb = () => (callbackRef.current as () => void)();
if (event === "prev") { if (event === "prev") {
const cb = callbackRef.current as () => void;
player.onPrev.add(cb); player.onPrev.add(cb);
return () => { return () => {
player.onPrev.delete(cb); player.onPrev.delete(cb);
}; };
} }
if (event === "next") { if (event === "next") {
const cb = callbackRef.current as () => void;
player.onNext.add(cb); player.onNext.add(cb);
return () => { return () => {
player.onNext.delete(cb); player.onNext.delete(cb);
+13 -2
View File
@@ -1,6 +1,12 @@
import { createPlayer } from "@videojs/react"; import { createPlayer } from "@videojs/react";
import { videoFeatures } from "@videojs/react/video"; import { videoFeatures } from "@videojs/react/video";
import { createContext, type ReactNode, useContext, useEffect } from "react"; import {
createContext,
type ReactNode,
useContext,
useEffect,
useRef,
} from "react";
import { WebOmniPlayer } from "./player.web"; import { WebOmniPlayer } from "./player.web";
import type { OmniPlayer } from "./types/player"; import type { OmniPlayer } from "./types/player";
import type { Source } from "./types/source"; import type { Source } from "./types/source";
@@ -39,10 +45,15 @@ const PlayerInitializer = ({
}) => { }) => {
const store = VideoPlayer.usePlayer(); const store = VideoPlayer.usePlayer();
const player = useLazyRef(() => new WebOmniPlayer(store)); const player = useLazyRef(() => new WebOmniPlayer(store));
const seekedForSrc = useRef<string | undefined>(undefined);
useEffect(() => { useEffect(() => {
player.source = source; player.source = source;
if (source.startTime) store.seek(source.startTime).catch(() => {}); const uri = source.src[0]?.uri;
if (uri !== seekedForSrc.current) {
seekedForSrc.current = uri;
if (source.startTime) store.seek(source.startTime)
}
}, [source, store]); }, [source, store]);
useEffect(() => { useEffect(() => {
+17 -8
View File
@@ -108,21 +108,29 @@ export const OmniView = ({
const ref = useRef<HTMLVideoElement>(undefined!); const ref = useRef<HTMLVideoElement>(undefined!);
const src = player.source?.src[0]; const src = player.source?.src[0];
const Tech = src?.uri.endsWith("m3u8") ? HlsJsVideo : Video; const isHls =
src?.mimeType?.toLowerCase().includes("mpegurl") ||
src?.uri.split(/[?#]/)[0]?.toLowerCase().endsWith(".m3u8") ||
false;
const Tech = isHls ? HlsJsVideo : Video;
const config = useMemo<HlsMediaConfig | undefined>(() => { const headersRef = useRef(src?.headers);
const headers = src?.headers; headersRef.current = src?.headers;
if (!headers || Object.keys(headers).length === 0) return undefined;
return { const config = useMemo<HlsMediaConfig>(
() => ({
hlsJs: { hlsJs: {
xhrSetup: (xhr: XMLHttpRequest) => { xhrSetup: (xhr: XMLHttpRequest) => {
const headers = headersRef.current;
if (!headers) return;
for (const [key, value] of Object.entries(headers)) { for (const [key, value] of Object.entries(headers)) {
if (value) xhr.setRequestHeader(key, value); if (value) xhr.setRequestHeader(key, value);
} }
}, },
}, },
}; }),
}, [src?.headers]); [],
);
return ( return (
<VideoPlayer.Container <VideoPlayer.Container
@@ -133,7 +141,7 @@ export const OmniView = ({
<Tech <Tech
ref={ref} ref={ref}
src={src.uri} src={src.uri}
config={config} config={isHls ? config : undefined}
autoPlay={autoplay} autoPlay={autoplay}
playsInline playsInline
crossOrigin="anonymous" crossOrigin="anonymous"
@@ -144,6 +152,7 @@ export const OmniView = ({
.map((subtitle) => ( .map((subtitle) => (
<track <track
key={subtitle.id} key={subtitle.id}
id={subtitle.id}
kind="subtitles" kind="subtitles"
src={subtitle.link} src={subtitle.link}
srcLang={subtitle.language} srcLang={subtitle.language}