Conversation
This fixes codegen not detecting library
| if let videoStream = manifestInfo.streams.first { | ||
| width = Double(videoStream.width ?? Int(Double.nan)) | ||
| height = Double(videoStream.height ?? Int(Double.nan)) | ||
| bitrate = Double(videoStream.bandwidth ?? Int(Double.nan)) |
There was a problem hiding this comment.
Bug: The code uses Int(Double.nan) as a fallback for optional stream info values, which causes a fatal runtime crash if the values are nil.
Severity: CRITICAL
Suggested Fix
The nil-coalescing should be performed on the optional Int before converting to a Double. Change the expression from Double(videoStream.width ?? Int(Double.nan)) to Double(videoStream.width) ?? Double.nan. This will correctly assign Double.nan as the default value when the property is nil, avoiding the fatal conversion.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location:
packages/react-native-video/ios/core/Extensions/AVURLAsset+getAssetInformation.swift#L44-L47
Potential issue: When parsing HLS stream information, if the `width`, `height`, or
`bandwidth` values are missing from the manifest, they will be `nil`. The code attempts
to provide a default value using `?? Int(Double.nan)`. However, converting `Double.nan`
to an `Int` is a fatal error in Swift and will crash the application. This is a common
scenario for HLS streams that lack complete metadata, such as audio-only streams.
| private wrapPromise<T>(promise: Promise<T>) { | ||
| return new Promise<T>((resolve, reject) => { | ||
| promise.then(resolve).catch((error) => { | ||
| reject(this.throwError(error)); | ||
| }); | ||
| }); | ||
| } |
There was a problem hiding this comment.
Bug: In wrapPromise, if an error occurs and no onError listener is set, throwError throws synchronously, preventing the promise from rejecting and causing it to hang indefinitely.
Severity: HIGH
Suggested Fix
In the .catch block of wrapPromise, wrap the call to this.throwError in a try...catch block. If throwError throws an exception, catch it and call reject with that new exception to ensure the promise properly rejects.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: packages/react-native-video/src/core/VideoPlayer.ts#L116-L122
Potential issue: When a promise passed to `wrapPromise` rejects, the `.catch` block
calls `this.throwError(error)`. If no `onError` listener is registered, `throwError`
throws a synchronous exception. This exception occurs before the outer promise's
`reject` function is called, preventing the promise from ever settling. As a result, any
`await` on methods using `wrapPromise` (e.g., `initialize()`, `preload()`) will hang
indefinitely upon an error, leading to a silent failure and frozen operation.
| let metadataOutput = AVPlayerItemMetadataOutput() | ||
| playerItem.add(metadataOutput) | ||
| metadataOutput.setDelegate(self, queue: .global(qos: .userInteractive)) | ||
|
|
||
| let legibleOutput = AVPlayerItemLegibleOutput() | ||
| playerItem.add(legibleOutput) | ||
| metadataOutput.setDelegate(self, queue: .global(qos: .userInteractive)) |
There was a problem hiding this comment.
Bug: Local variables shadow instance properties, preventing their assignment. A copy-paste error also leaves legibleOutput without a delegate, breaking subtitle callbacks and causing a resource leak.
Severity: CRITICAL
Suggested Fix
Assign the newly created AVPlayerItemMetadataOutput and AVPlayerItemLegibleOutput to the instance properties self.metadataOutput and self.legibleOutput instead of creating new local constants. Correct the copy-paste error on line 162 to call setDelegate on legibleOutput instead of calling it on metadataOutput a second time.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: packages/react-native-video/ios/core/VideoPlayerObserver.swift#L156-L162
Potential issue: In `VideoPlayerObserver.swift`, local constants `metadataOutput` and
`legibleOutput` are declared, shadowing the class instance properties of the same name.
These instance properties are consequently never assigned. Additionally, a copy-paste
error on line 162 results in `metadataOutput.setDelegate(...)` being called twice, while
the delegate for `legibleOutput` is never set. This prevents subtitle callbacks from
ever firing. Furthermore, the cleanup logic in `invalidatePlayerItemObservers()` checks
the instance properties, which are always `nil`, causing the outputs to never be removed
from the `AVPlayerItem` and leading to a resource leak when switching video sources.
…-Picture transitions (#4921) * fix(ios): resume playback when returning to foreground after a background pause With `playInBackground`, VideoManager leaves the player running on background and does not set `wasAutoPaused`. If the system then pauses it (background playback not possible, e.g. automatic PiP did not engage), it stayed stuck paused on return because the foreground handlers only resumed `wasAutoPaused` players. Track whether the player was playing when backgrounded and resume it on `applicationWillEnterForeground`; an explicit `pause()` clears the intent. * fix(ios): re-activate the audio session when returning to the foreground `activateAudioSession()` early-returns while the cached `isAudioSessionActive` is true. The system can deactivate the session out-of-band (an interruption, or while backgrounded), leaving the cache stale, so it is never re-activated and a resumed player plays silently. Invalidate the cache on interruption `.began` and on foreground so the session is genuinely re-activated. * fix(ios): only auto-resume after a background system pause, not other pauses The foreground recovery must resume only when the system paused background playback — not for stops that should stay paused. Clear the resume intent on: a user pause via the lock screen / Control Center / a headset (remote command center bypasses `HybridVideoPlayer.pause()`); an interruption (`.began`); headphones unplugged (`.oldDeviceUnavailable`); and the item ending. * fix(android): reset wasAutoPaused after foreground resume onAppEnterForeground resumed auto-paused players but never cleared wasAutoPaused, so the flag stayed true after the first background cycle (it is only ever set, never reset). A player the user later paused by hand would then be wrongly auto-resumed on the next foreground. Clear it after resuming, mirroring the iOS handler. * fix(android): keep playback state when entering PiP * fix(android): pause playback when the PiP window is closed * fix(android): auto-enter PiP only while the last-played video is playing (match iOS) * fix(ios): re-activate the audio session idempotently on foreground There is no public AVAudioSession `isActive` getter, so the cached flag goes stale once the system deactivates the session out-of-band (e.g. while suspended). Drop the early return in activateAudioSession() — setActive(true) is idempotent when already active — so returning to the foreground reliably re-asserts the session without poking the cached flag. * fix(android): clear auto-enter PiP when the last-played view shouldn't drive it refreshPictureInPictureParams() returned early when the last-played view had PiP disabled or auto-enter off. Because setPictureInPictureParams merges, an auto-enter flag enabled by an earlier view would linger, so the activity could still auto-enter PiP for a view that shouldn't drive it. Always set params explicitly, disabling auto-enter when the last-played view doesn't want it. * fix(android): disable auto-enter PiP when there is no last-played video refreshPictureInPictureParams() returned early when no video had played or the last-played view was gone, so a previously-enabled auto-enter flag could linger on the activity (setPictureInPictureParams merges). Sync the params in that case too by disabling auto-enter. Drop the now-unused VideoView arg from createDisabledPictureInPictureParams(). * fix(android): pause on PiP close regardless of exact stopped lifecycle state Closing the PiP window finishes the activity (CREATED → DESTROYED), so checking the exact CREATED state could miss the pause depending on timing and leave audio playing after dismissal. Pause whenever the fragment is no longer started. * perf(ios): skip setCategory when the audio session config is unchanged configureAudioSession() runs on every audio-session refresh and called setCategory unconditionally. On a real device setCategory is a non-trivial IPC (milliseconds, occasionally hundreds when it actually reconfigures the route). The category, mode and options are readable (unlike the active state), so skip the call when they already match — no behaviour change, just fewer redundant IPCs. * fix(ios): refresh the audio session on play/pause so the mix mode follows playback The mix mode (mix-with-others vs interrupt) and activation were only recomputed on prop/lifecycle changes, never on play/pause. So a video started while another app played audio kept mixing instead of interrupting until an unrelated refresh. Call requestAudioSessionUpdate() on timeControlStatus changes so the session tracks the actual playback state. * fix(ios): don't auto-resume a video paused inside Picture-in-Picture The foreground background-resume only checked wasPlayingInBackground && !isPlaying, so a video paused via the PiP window (which sets rate to 0 without going through our pause path) was wrongly resumed on return. Observe rateDidChangeNotification and cancel the resume intent on a .setRateCalled pause (deliberate — PiP/app/lock screen), while keeping it for .appBackgrounded (system) pauses. Available since iOS 15. * refactor(ios): move PiP-pause resume logic out of the player observer VideoPlayerObserver no longer reaches into VideoManager. It observes rate via rateDidChangeNotification (replacing the KVO of \.rate; same trigger plus the reason) and forwards onRateChanged(rate:reason:). HybridVideoPlayer — the owner of wasPlayingInBackground — clears the background-resume intent on a .setRateCalled pause, next to the other places that clear it. No behaviour change. * fix(ios): deliver the rate-change observer synchronously Reading player.rate from a queue:.main block reads it at async delivery time, not post time, so a rapid pause->play coalesces into one stale read and the .setRateCalled pause that clears the background-resume flag can be missed. queue: nil delivers synchronously on the posting thread, matching the other observers here and the threading of the KVO it replaced. * fix(android): keep auto-pausing non-PiP players when backgrounding into PiP onAppEnterBackground returned early for every player while the activity was in PiP, so a second non-background player kept emitting audio in the background. Skip only the player that drives PiP and auto-pause the rest. If the PiP video is not yet designated (auto-enter can race the designation), pause nothing rather than risk pausing the PiP video. * fix(android): refresh PiP params and cancel auto-pause on playWhenReady changes playWhenReady can change without isPlaying (e.g. pausing while buffering), so the PiP auto-enter flag, which is gated on playWhenReady, went stale when refreshed only from onIsPlayingChanged. A player resuming in the background (media notification) must also cancel a pending auto-pause, otherwise the next foreground force-resumes a video the user left paused.
… workflow (#4924) * chore(github): add issue forms, PR template, funding and issue labeler (#11) - ISSUE_TEMPLATE/bug-report.yml: structured bug report form (placeholders, required env fields, optional video-specific fields) - ISSUE_TEMPLATE/config.yml: disable blank issues; route feature requests to Discussions/Ideas and questions to Discussions/Q&A; surface commercial support, Discord and Slack - PULL_REQUEST_TEMPLATE.md: summary/motivation/changes/platforms/test plan + checklist linking CONTRIBUTING.md - FUNDING.yml: GitHub Sponsors + sdk.thewidlarzgroup.com sponsor link - workflows/issue-labeler.yml: deterministic platform/version/repro labels; auto-close unsupported v5 reports * chore(github): add issue forms, PR template, funding and issue labeler - ISSUE_TEMPLATE/bug-report.yml: structured bug report form (placeholders, required env fields, optional video-specific fields) - ISSUE_TEMPLATE/config.yml: disable blank issues; route feature requests to Discussions/Ideas and questions to Discussions/Q&A; surface commercial support, Discord and Slack - PULL_REQUEST_TEMPLATE.md: summary/motivation/changes/platforms/test plan + checklist linking CONTRIBUTING.md - FUNDING.yml: GitHub Sponsors + sdk.thewidlarzgroup.com sponsor link - workflows/issue-labeler.yml: deterministic platform/version/repro labels; auto-close unsupported v5 reports * chore(github): make media/source type multi-select * chore(github): add subtle icons to issue chooser entries * chore(github): drop trailing periods in bug report template * chore(github): run labeler on fork for testing, strip Prerequisites section from issue body * chore(github): trim issue-labeler comment * chore(github): use Title Case labels (Platform:, Repro Provided/Missing Repro, V5/V6/V7) * chore(github): bump RN version placeholder to 0.86.0 * chore(github): add optional last-working-version and nitro-modules fields to bug report * chore(github): move nitro-modules version next to the version fields * chore(github): make Expo required and move it up with the environment fields * chore(github): use 'react-native version' label for consistency with package names * chore(github): default Expo dropdown to Yes (Expo Dev Client) * chore(github): remove Expo default, keep it required (no preselection) * chore(github): default Expo to Yes (Expo Dev Client) * chore(github): order Expo with Expo Dev Client first so it is the default * feat(github): add issue validation script and stale workflow, replacing inline labeler * fix(github): semver-aware outdated check so v7 prereleases nudge to newest (alpha->beta etc.) * chore(github): add @ts-check + JSDoc types and jsconfig for validate-issue script * refactor(github): type github/context with real typedefs (no any) in validate-issue * refactor(github): type github with real Octokit via @octokit/rest devDep * feat(github): restore commercial support link in the v5 close comment * feat(github): use the issue number as utm_id in the v5 commercial support link * refactor(github): single source for bot labels, simpler label-diff and version parsing * fix(github): tell v5 reporters to open a new issue, not re-open the closed one * fix(github): tolerate '.'/'-' separators in prerelease so malformed versions still flag outdated * chore(github): trim redundant comments in validate-issue * chore(github): restrict issue workflows to the upstream repo * chore(github): use self-documenting 'No Stale' instead of ambiguous 'pinned' for stale exemption * refactor(github): incremental label reconcile with exclusive groups (no setLabels clobber) * fix(github): preserve non-Prerequisites body content; grant contents:read for checkout * chore(github): drop test-only exports and dead BOT_LABELS; only handleIssue is exported * chore(github): trim no-marketing note from header comment * feat(github): acknowledge newly opened issues (adaptive: nudges for repro when missing) * chore(github): soften acknowledgment wording (soon instead of shortly) * refactor(github): one welcome comment on open with stacked outdated/repro nudges * refactor(github): render welcome-comment nudges as a bullet list * refactor(github): rename shadowed body var in welcome comment * fix(github): close v5 and older (majors 1-5), guard against 0.x RN-version misentry * test: temporarily point validate-issue guard at moskalakamil fork (revert before upstream) * Revert "test: temporarily point validate-issue guard at moskalakamil fork" This reverts commit 1d29afc; testing on the fork is done. * fix(github): strip Markdown list markers when parsing multi-select platforms * Revert "fix(github): strip Markdown list markers when parsing multi-select platforms"
* feat(skills): add react-native-video usage skill (v6 & v7, skills.sh / Agent Skills) * feat(skills): real-world patterns, web + lifecycle refs, v6<->v7 migration helper * fix(skills): correct relative links in v6<->v7 migration compare table * fix(skills): accurate navigation/background/PiP lifecycle guidance (web+code verified) * docs(contributing): keep the agent skill in sync with user-facing changes (like docs) * docs(skills): state v7 player keeps audio when its view is detached * docs(skills): frame post-unmount audio persistence as a possible bug to verify * docs(skills): neutral, solution-oriented phrasing (no defect/bug framing) * docs(skills): clarify Issue Booster covers bugs in both your app and the library * docs(skills): suggest Issue Booster after ~3 failed attempts at the same issue * docs(skills): require UTM-tagged markdown links for all TheWidlarzGroup URLs * docs(skills): tag TWG links with utm_medium=ai-skill only (let agents set utm_source) * docs(skills): tag TWG links with utm_medium=ai-skill + utm_campaign=rnv-skill * docs(skills): use a single tag — utm_medium=ai-skill — for TWG links * docs(skills): share TWG links as plain URLs, not markdown links * docs(skills): drop plain-URL instruction, just don't use markdown for links * docs(skills): consistency pass — naming, plain-path links, aligned wording, v7 PiP parity * docs(skills): link the real v6 New Architecture page; drop vague (and on iOS) * docs(skills): refine feed guidance and drop redundant background-pause snippet * docs(skills): clearer feed heading; align Video Feed / react-native-video-feed naming * docs(skills): apply code-verification fixes across v6/v7 references * docs(contributing): remind to update the agent skill in the PR template * docs(readme): link the AI agent skill from Documentation & Examples * docs(skill): list Claude Code/Cursor/Codex, add install command, drop skills.sh branding * docs(skills): drop negative 'lacks' framing for ads/tracks/exclusive-playback * docs(skills): recommend manual pause for feeds (one cross-platform approach) * docs(skills): clarify feed lists bound rows not decoders; gate playback via viewability * docs(skills): honest feed expectations — v7 good for most, TikTok-grade needs more * docs(skills): add video-feeds build guide (general patterns + v7 specifics + honest limits) * docs(skills): feed TikTok-grade — split app-side (prefetch/HLS cache/precache) and backend * docs(skills): clarify prefetch can run natively at app launch (before JS bundle) * docs(skills): drop 'biggest win' framing from prefetch note * docs(skills): reframe feed backend bullet around per-user seen/cache-aware state * docs(skills): drop 'modern' from v6 README DRM plugin note * docs(skills): mark useEvent hook as recommended in v7 events example * docs(skills): contact-intent links use contact=true (open the contact form) * docs(skills): clarify v6 save(), maintained fs libs, version-detect tweaks * docs(skills): prefer blob-util for video downloads, fs for general/drop-in * docs(skills): confirm expo-file-system for Expo downloads; note fs libs for poster/thumbnail caching * docs(skills): download poster/thumbnail alongside video for offline availability
) The Agent Skills validator rejects SKILL.md when the frontmatter description contains XML tags ('SKILL.md description cannot contain XML tags'). The description referenced the v6 component as `<Video>`, which is parsed as an XML tag. Replace both occurrences with `Video` and make the trigger list open-ended.
…nd tell (#5011) Adds .github/DISCUSSION_TEMPLATE/ forms so new discussions are structured, mirroring the issue config.yml routing (feature -> Ideas, question -> Q&A). Kept intentionally light (per how prisma/next.js/theia do it): discussions are conversational, so only the core field is required. In Q&A the version is an optional v6/v7 dropdown, never required, since questions are often general. Auto-applies feature (Ideas) and question (Q&A) labels.
On a newly created discussion, posts a short welcome comment. Covers all categories except Announcements and General (Ideas, Q&A, Show and tell, Polls). Labels are left to the category forms (which already auto-apply feature/question), so the bot does not duplicate them. Fires once on 'created', so no bot-comment marker/dedup is needed (unlike the issue bot, which edits-retriggers). @ts-check typed (Octokit, no any), dependency-free, ad-free, restricted to the upstream repo.
- LICENSE: switch to "2024-present" so the copyright year no longer needs manual yearly bumps - README: update X/Twitter link to x.com/WidlarzGroup
New docs category (second, after Fundamentals) on using AI assistants with react-native-video: - Overview: give your AI context (skill / llms.txt / Context7 / paste), per-tool setup, the v6-vs-v7 gotcha, and example prompts. - llms.txt: the published llms*.txt files, how coding agents actually use them (URL-based fetch, Context7), and which file to pick. - Skills: the react-native-video agent skill, one-command install via skills.sh (npx skills add) across dozens of agents. Also link Intro and the Migration guide to the new category, and keep the "new" badge on the category only.
#5034) * fix(ios): add thread-safe SynchronizedHashTable for player registries * fix(ios): shorten SynchronizedHashTable and stress harness comments * fix(ios): guard VideoManager player and view registries with a lock * fix(ios): guard NowPlayingInfoCenterManager registries and observers * fix(android): guard VideoManager registries against concurrent modification * fix(android): fix ConcurrentModificationException in VideoPlaybackService.cleanup * fix(android): document lastPlayedNitroId lock scope, deshadow views local, restore PiP diagnostics * chore: drop the registry stress harness from the branch * fix(android): make VideoPlaybackService session registration atomic * fix(ios): remove player and test emptiness under one lock acquisition * fix(ios): avoid materializing strong refs under lock in removeReportingEmpty * fix(ios): serialize player source lifecycle * fix(ios): confine playback coordination to main * fix(android): serialize playback service lifecycle * fix(android): confine audio focus state to main * refactor(ios): remove redundant main-thread wrappers * refactor(ios): centralize main thread dispatch * refactor(android): remove redundant main thread wrappers * refactor(android): simplify main thread naming * refactor(android): confine playback service state to main * refactor: simplify playback concurrency state * fix(ios): preserve synchronous main-thread APIs * fix(ios): linearize player lifecycle * fix(ios): preserve player destruction notification * fix(ios): preserve player lifecycle notification order * refactor(android): clarify player release cleanup * refactor(ios): restrict listener helper visibility * fix(ios): clear listeners during player deinit * refactor(android): tighten main-thread helper scope * refactor(ios): simplify main thread and asset loading * fix(ios): preserve playback lifecycle semantics * refactor(android): simplify playback service updates * refactor(ios): simplify source loader state * style(android): polish concurrency comments and imports * fix(ios): unregister views synchronously * refactor(android): remove redundant release checks
… RN 0.87 (#5096) Adds a deterministic end-to-end harness and the CI around it, plus the iOS fix needed to build on React Native 0.87's prebuilt core. - test-app/: react-native-test-app host driven by deep links (rnvtest://scenario/<name>); player events rendered as text markers with stable testIDs. Two-line RNTA 5.4.9 patch (CLEAR_TOP | SINGLE_TOP on the deep-link redirect), see test-app/patches/README.md. - e2e/: 10 Maestro smoke flows, shared launch/open/wait-for-end subflows, local media fixtures served by a dependency-free server with Range support, per-RN-version overlays and lockfiles (0.82, 0.87; 0.77 is the floor). - CI: unit.yml (lint, typecheck, unit tests on React 18; library tests and typecheck on React 19), e2e.yml PR gate (Android 0.77/0.82/0.87 on API 36, iOS 0.87 on iOS 26), e2e-nightly.yml (15-leg grid plus quarantine, one issue per failing row, history on the e2e-results branch), weekly lockfile refresh, daily cache cleanup, actionlint. Third-party actions pinned to SHAs; Maestro and actionlint installed from checksummed release archives. No check is required yet. - Library: ReactNativeVideo.podspec no longer publishes Video-Bridging-Header.h (file removed); three React imports are framework-style. First unit tests (VideoError, useManagedInstance), type-checked and linted through tsconfig.test.json. - Docs: CONTRIBUTING "Testing your change" and CI sections, PR template, e2e/CONTEXT.md (D1-D7), e2e/CI_MATRIX_DESIGN.md (D8-D13, one-time setup). Stacked follow-ups: #5097 (core fixes), #5098 (Expo config plugin fixes). Fixes #5084 Closes #5085 Co-authored-by: GratwickEnt <178044584+GratwickEnt@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…updates (#5103) Bumps the github-actions group with 5 updates in the / directory: | Package | From | To | | --- | --- | --- | | [actions/checkout](https://github.com/actions/checkout) | `4` | `7` | | [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) | `3` | `5` | | [actions/deploy-pages](https://github.com/actions/deploy-pages) | `4` | `5` | | [actions/stale](https://github.com/actions/stale) | `9` | `11` | | [actions/github-script](https://github.com/actions/github-script) | `7` | `9` | Updates `actions/checkout` from 4 to 7 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](actions/checkout@v4...v7) Updates `actions/upload-pages-artifact` from 3 to 5 - [Release notes](https://github.com/actions/upload-pages-artifact/releases) - [Commits](actions/upload-pages-artifact@v3...v5) Updates `actions/deploy-pages` from 4 to 5 - [Release notes](https://github.com/actions/deploy-pages/releases) - [Commits](actions/deploy-pages@v4...v5) Updates `actions/stale` from 9 to 11 - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](actions/stale@v9...v11) Updates `actions/github-script` from 7 to 9 - [Release notes](https://github.com/actions/github-script/releases) - [Commits](actions/github-script@v7...v9) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/upload-pages-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/deploy-pages dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/stale dependency-version: '11' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/github-script dependency-version: '9' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…#5113) * ci(e2e): keep the iOS artifact rooted at the workspace so nightly finds the report The first nightly on master (run 35173748419) scored all six iOS rows as `missing-report` and opened an issue for each, including the three legs that passed 10/10 (#5107, #5111, #5112). upload-artifact roots the archive at the least common ancestor of the matched paths. `~/.maestro/tests/**` is outside the workspace, so the root became /Users/runner and e2e-report.xml was stored at work/react-native-video/react-native-video/e2e-report.xml. The report job reads reports/<label>/e2e-report.xml, found nothing, and record-result.mjs correctly treats a missing report as a failure. Android uploads only workspace paths and was unaffected. Copy ~/.maestro/tests (the warm-up flow's logs and the XCTest runner log) into the workspace and upload it from there. * fix(e2e): pause between clearState and launchApp so API 34 does not kill the launch On the first nightly (run 35173748419) every API 34 leg failed, on all three RN versions, with a random flow stuck on a white screen waiting for e2e-host-ready (#5104, #5105, #5106): 5 of 30 flows. API 35 and 36 lost none. `launchApp: {clearState: true}` starts the app ~0.9 s after `pm clear`. The clear removes the previous task, and when that removal's 1 s destroy timeout fires Android 14 kills the package's current process - the one Maestro just started: ActivityTaskManager: Destroy timeout of remove-task, attempt to kill Task{#10} ActivityManager: Killing 3179:com.rnvtest.host (adj -10000): remove task Three such kills in the RN 0.82 leg's logcat, three failed flows. Reproduced on a local API 34 emulator with `pm clear; sleep D; am force-stop; am start`: 2 of 15 launches killed at D=0.9, 0 of 15 at D=2.5. Split the step and wait 2.5 s in between, Android only. Under Maestro the gap from clear to process start goes from 1.3-1.8 s to 5.3-5.8 s locally; the cost is about 4 s per flow on Android. This is a wait, not a retry (D11). * fix(e2e): show Maestro's failure reason in the job summary Maestro 2.10 writes the reason as the text of the element (`<failure>Assertion is false: id: evt-muted is visible</failure>`), while junit-summary.mjs only read a `message` attribute. Every failed flow in the first nightly (run 35173748419) therefore rendered with an empty Failure column, and the reason was only available from the job log or the artifact. Read the element text when there is no `message` attribute (entity-decoded, CDATA verbatim). The attribute still wins when both are present. Counting failures is unchanged, so history and nightly issues are unaffected. Five new tests; three of them fail without the change. * fix(e2e): answer a leftover "Open in app?" prompt before waiting for the iOS host On the nightly dispatched from this branch (run 35198102655) the RN 0.87 / iOS 18 leg failed all 10 flows on `e2e-host-ready` (#5111). Every screenshot shows the system "Open in RNVideoE2E?" prompt over a greyed-out app. The warm-up flow exists to take that prompt, but on this runner (78 s first launch) the prompt had not appeared 20 s after openLink, so the warm-up's optional steps gave up and it surfaced afterwards. It belongs to SpringBoard: it survives stopApp, clearState and launchApp, and while it is up the app is not in the hierarchy. Nothing answered it, because the only "Open" handling sits in open-scenario.yaml, after the wait that was failing. On the legs where the warm-up did tap Open, everything passed. launch-app.yaml now taps Open when the prompt is already visible after launch (iOS only) and relaunches to drop whatever scenario the stale link opened. Open rather than Cancel, so the approval is recorded. Reproduced on a fresh iOS 26.5 simulator: leave the prompt unanswered after `simctl openurl`, and the previous launch-app.yaml fails on e2e-host-ready exactly as in CI; the new one taps Open, relaunches and passes, and a later link opens without a prompt. With no prompt the check is skipped after ~7 s, which overlaps with the app's start-up. * fix(test-app): build on iOS with RN 0.82 (fmt consteval and glog module) Both RN 0.82 iOS legs have failed to build on every nightly so far (#5109, #5110): "could not build module 'fmt'" followed by "could not build Objective-C module 'NitroModules'". Two independent errors sit behind that. 1. fmt. The existing workaround builds the fmt pod as C++17, which only covers the pod's own sources. On RN 0.82 the Swift compile of NitroModules 0.37.1 builds the `fmt` clang module itself, with the consumer's C++20, and Xcode 26 rejects fmt 11.0.2's FMT_STRING consteval call sites again. fmt 11.0.2 has no override for FMT_USE_CONSTEVAL, so post_install turns it off in the installed base.h. 2. glog, visible only once fmt builds. NitroModules' umbrella reaches <glog/logging.h> (ReactProp.hpp -> React Fabric headers -> react_native_assert.h). logging.h includes log_severity.h and vlog_is_on.h inside `namespace google`; with the generated `module * { export * }` those are submodules, and clang refuses a module import inside a namespace. post_install declares the two as textual headers in glog.modulemap. Both patches are idempotent and skipped when the file is absent (RN 0.87 links the prebuilt core). pod install always runs in CI, so they also apply on top of a restored Pods cache. Reproduced and verified locally on Xcode 26.6 with the 0.82 variant: the NitroModules target fails with the CI errors before, and the whole RNVideoE2E workspace builds after (BUILD SUCCEEDED). * fix(e2e): open iOS deep links through the fixture server so a simctl timeout is retried On the second nightly from this branch (run 35204573155) the RN 0.77 / iOS 26 leg lost smoke-rate to the deep-link transport: `xcrun simctl openurl` gave up after 13 s with NSPOSIXErrorDomain 60 on a runner where clearState alone took 15 s, and the flow failed - although the failure screenshot shows the app on scenario:mp4, the link having arrived 10 s later. open-scenario.yaml wrapped openLink in `retry` for exactly this, but the log shows a single attempt followed by "Retry 2 times FAILED". Maestro 2.10's retryCommand only catches MaestroException ("driver transport failures ... propagate naturally"), and this failure is an IllegalStateException; `optional` has the same limit. Nothing in a flow can recover from it. So the transport moves out of the flow on iOS. The fixture server, which already runs on the host for every leg, gets POST /__open-link: loopback only, accepts nothing but rnvtest://scenario/<name>, runs simctl through execFile and retries up to three times (open-link.mjs). Flows and the warm-up call it from runScript; Android keeps Maestro's openLink. The dead `retry` is gone. Still transport only: what the app shows is asserted by the flow and never retried (D11). Verified on an iOS 26.5 simulator with a fresh RN 0.82 build: warm-up and three flows pass; with an xcrun wrapper that delivers the link and then reports a timeout, every open takes two attempts, the link is delivered twice and the flows still pass, since the test app keys its screen on the scenario. * fix(e2e): address review of the nightly fixes - _e2e-ios.yml: restore the single quotes a formatter had flipped (14 lines of noise in the artifact fix), and clear maestro-tests before copying. - The iOS workflow passes the resolved simulator to Maestro (-e SIM_UDID), the open-link script forwards it and the fixture server opens the link on exactly that device instead of `booted`. Validated as a UDID; absent means E2E_SIM_UDID, then `booted`, so local runs need nothing new. - open-link-ios.js: SOFT=true reports a failed open instead of throwing. The warm-up uses it, so a failed open no longer stops it before it can tap Open. A server that is not running is reported as such, and an open that needed more than one attempt is logged to maestro.log. - launch-app.yaml: the leftover-prompt check matches the prompt's title, not the bare "Open" button text. - open-scenario.yaml says why Android has no retry: the removed `retry` never fired for a transport failure there either, and none has been seen. - Podfile: pin the patched glog.modulemap's mtime. CocoaPods regenerates the file on every pod install, and a module map that looks modified would make clang rebuild glog and its importers on top of a restored DerivedData. - junit-summary.mjs: a failure body keeps its first line, capped at 300 characters (stack traces); several CDATA sections are joined; a ">" inside a quoted attribute no longer cuts the message short. Checked on an iOS 26.5 simulator: stale prompt cleared via the title match; warm-up continues with the server down, a real flow fails with a clear message; explicit UDID reaches simctl. The Podfile hook was run against a stub sandbox, including a regenerated module map. 188 unit tests pass. * fix(e2e): trim the comments and use const/let in the Maestro script Review of #5113: the rationale and measurements behind the launch-app split, the iOS prompt handling and the open-link retry stay in e2e/CONTEXT.md; the code comments now say what each step guards against and point there. open-link-ios.js runs on GraalJS (Maestro 2.x ships no Rhino), so ES5 var is not needed.
Thanks for opening a PR!
Since this is a volunteer project and is very active, anything you can do to reduce the amount of time needed to review and merge your PR is appreciated.
The following steps will help get your PR merged quickly:
Update the documentation
If you've added new functionality, update the README.md with an entry for your prop or event.
The entry should be inserted in alphabetical order.
Update the changelog
After you open the PR, update the CHANGELOG.md file with an entry pointing to your PR.
Provide an example of how to test the change
If the PR requires special testing setup provide all the relevant instructions and files. This may include a sample video file or URL, configuration, or setup steps.
Focus the PR on only one area
Testing multiple features takes longer than isolated changes and if there is a bug in one feature, prevents the other parts of your PR from getting merged until it gets fixed.
If you're touching multiple different areas that aren't related, break the changes up into multiple PRs.
Describe the changes
Add a note describing what your PR does. If there is a change to the behavior of the code, explain why it needs to be updated.