From 40340467d7df2ad606f25606041630c489c385f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Moska=C5=82a?= <91079590+moskalakamil@users.noreply.github.com> Date: Mon, 14 Apr 2025 15:47:15 +0200 Subject: [PATCH 01/47] fix(tvos): build (#4511) --- ios/Video/Features/RCTPlayerObserver.swift | 26 ++++++++++++---------- ios/Video/RCTVideo.swift | 2 +- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/ios/Video/Features/RCTPlayerObserver.swift b/ios/Video/Features/RCTPlayerObserver.swift index 019fa5ea74..230ff76890 100644 --- a/ios/Video/Features/RCTPlayerObserver.swift +++ b/ios/Video/Features/RCTPlayerObserver.swift @@ -322,19 +322,21 @@ class RCTPlayerObserver: NSObject, AVPlayerItemMetadataOutputPushDelegate, AVPla _restoreUserInterfaceForPIPStopCompletionHandler = completionHandler } - func playerViewController( - _: AVPlayerViewController, - willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator - ) { - // iOS automatically pauses videos after exiting fullscreen, - // but it's better if we resume playback - let wasPlaying = player?.timeControlStatus == .playing - - coordinator.animate(alongsideTransition: nil) { [weak self] context in - guard let self, !context.isCancelled, wasPlaying else { return } - self.player?.play() + #if !os(tvOS) + func playerViewController( + _: AVPlayerViewController, + willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator + ) { + // iOS automatically pauses videos after exiting fullscreen, + // but it's better if we resume playback + let wasPlaying = player?.timeControlStatus == .playing + + coordinator.animate(alongsideTransition: nil) { [weak self] context in + guard let self, !context.isCancelled, wasPlaying else { return } + self.player?.play() + } } - } + #endif func setRestoreUserInterfaceForPIPStopCompletionHandler(_ restore: Bool) { guard let _restoreUserInterfaceForPIPStopCompletionHandler else { return } diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index f029ea0579..4df3c2c721 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -1167,7 +1167,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH viewController.player = player // Set the initial playback speed in controls to match playback rate - if #available(iOS 16.0, *) { + if #available(iOS 16.0, tvOS 16.0, *) { if let initialSpeed = viewController.speeds.first(where: { $0.rate == _rate }) { viewController.selectSpeed(initialSpeed) } From b17c319c32ad8080ad911ec4be7fa02bc4a6d6ee Mon Sep 17 00:00:00 2001 From: Ryan Fowler Date: Mon, 14 Apr 2025 07:48:15 -0600 Subject: [PATCH 02/47] fix(macCatalyst): allowsVideoFrameAnalysis not available in catalyst until 18.0 (#4515) --- ios/Video/RCTVideo.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 4df3c2c721..369d465f5f 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -561,7 +561,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } } else { #if !os(tvOS) && !os(visionOS) - if #available(iOS 16.0, *) { + if #available(iOS 16.0, macCatalyst 18.0, *) { // This feature caused crashes, if the app was put in bg, before the source change // https://github.com/TheWidlarzGroup/react-native-video/issues/3900 self._playerViewController?.allowsVideoFrameAnalysis = false @@ -569,7 +569,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH #endif _player?.replaceCurrentItem(with: playerItem) #if !os(tvOS) && !os(visionOS) - if #available(iOS 16.0, *) { + if #available(iOS 16.0, macCatalyst 18.0, *) { self._playerViewController?.allowsVideoFrameAnalysis = true } #endif From 2bdc0daf2ef101bceb33d6fb2dafd63945aaf063 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Moska=C5=82a?= <91079590+moskalakamil@users.noreply.github.com> Date: Wed, 16 Apr 2025 09:30:52 +0200 Subject: [PATCH 03/47] chore: update bot messages (#4517) --- .github/scripts/validate.js | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/.github/scripts/validate.js b/.github/scripts/validate.js index 6f4cb3d049..da11007be6 100644 --- a/.github/scripts/validate.js +++ b/.github/scripts/validate.js @@ -30,23 +30,33 @@ const BOT_LABELS = [ const SKIP_LABEL = 'No Validation'; +const ISSUE_BOOST_INFO = ` +Need faster resolution? Consider [Issue Boost](https://www.thewidlarzgroup.com/issue-boost/?utm_source=rnv&utm_medium=bug-report&utm_campaign=bot&utm_id=bot-message) โ€“ it allows us to dedicate time specifically to your issue and fix it faster ๐Ÿš€`; + const MESSAGE = { - FEATURE_REQUEST: `Thanks for the feature request! Check out our roadmap [here](https://github.com/TheWidlarzGroup/react-native-video/discussions/3351). If your request is already there โ€“ great! If not, give us some time, and we'll get back to you with information on when TheWidlarzGroup can address it as part of our free open-source support. Alternatively, [contact us](https://www.thewidlarzgroup.com/?utm_source=rnv&utm_medium=feature-request&utm_campaign=bot&utm_id=bot-message#Contact) to discuss ways to speed up the process.`, - BUG_REPORT: `Thank you for your bug report. We will review it and get back to you if we need more information.`, + FEATURE_REQUEST: `Thanks for the feature request! ๐Ÿš€ + \nYou can check out our [public roadmap](https://github.com/orgs/TheWidlarzGroup/projects/6) to see what we're currently working on. All requests are automatically added there, so you can track progress anytime. + \nWe review and implement new features when time allows, but this can take a while. If youโ€™d like to speed things up and make this a priority, consider [Issue Boost](https://www.thewidlarzgroup.com/issue-boost/?utm_source=rnv&utm_medium=feature-request&utm_campaign=bot&utm_id=bot-message), our commercial option that lets us dedicate time specifically to your request. + \nThanks for your input and patience! ๐Ÿ™Œ`, + BUG_REPORT: `Hey! ๐Ÿ‘‹ + Thanks for reporting this issue. We try to fix bugs as quickly as possible, but since our time is limited, we prioritize sponsored issues first, then focus on critical problems affecting many users, and finally, we handle other reports when we can. Some issues might take a while to be resolved. + \nIf you want to speed up this process, check out [Issue Boost](https://www.thewidlarzgroup.com/issue-boost/?utm_source=rnv&utm_medium=bug-report&utm_campaign=bot&utm_id=bot-message-valid) โ€“ it allows us to dedicate time specifically to your issue and fix it faster. + \nThanks for your patience and support! ๐Ÿš€`, MISSING_INFO: (missingFields) => { - return `Thank you for your issue report. Please note that the following information is missing or incomplete:\n\n${missingFields + return `Hey! ๐Ÿ‘‹ + Thanks for the bug report. To help us resolve your issue effectively, we still need some key information:\n\n${missingFields .map((field) => `- ${field.replace('missing-', '')}`) - .join( - '\n', - )}\n\nPlease update your issue with this information to help us address it more effectively. - \n > Note: issues without complete information have a lower priority`; + .join('\n')} + + Please edit your issue and fill in the missing details. + > Issues with incomplete info are treated with lower priority, so this helps speed things up.`; }, OUTDATED_VERSION: (issueVersion, latestVersion) => { return ( - `There is a newer version of the library available. ` + - `You are using version ${issueVersion}, while the latest stable version is ${latestVersion}. ` + - `Please update to the latest version and check if the issue still exists.` + - `\n > Note: If the issue still exists, please update the issue report with the latest information.` + `Heads up! โš ๏ธ You're using version **${issueVersion}**, but the latest stable version is **${latestVersion}**. ` + + `Please update to the newest version and check if the issue still occurs.\n\n` + + `> Keeping your dependencies up-to-date often resolves many common problems.` + + `\n\nStill having the issue after upgrading? Update the report with the new version details so we can investigate.` ); }, }; @@ -179,6 +189,7 @@ const handleFeatureRequest = async ({github, context, body, labels}) => { validateFeatureRequest(body, labels); const comment = MESSAGE.FEATURE_REQUEST; + await hidePreviousComments({github, context}); await createComment({github, context, body: comment}); }; @@ -212,6 +223,8 @@ const handleMissingInformation = async ({github, context, labels}) => { )}`; } + comment += `\n\n${ISSUE_BOOST_INFO}`; + await hidePreviousComments({github, context}); await createComment({github, context, body: comment}); } From b1b3db301097e82c8d449c08f7d334a2eaeac0ea Mon Sep 17 00:00:00 2001 From: Krzysztof Moch Date: Fri, 18 Apr 2025 18:02:55 +0200 Subject: [PATCH 04/47] feat(ios): add `overridePlayerAsset` to `AVPlayerPlugin` (#4522) --- docs/pages/other/plugin.md | 69 +++++++-- examples/expo/ios/Podfile.lock | 134 +++++++++--------- ios/Video/DataStructures/AdParams.swift | 2 +- ios/Video/DataStructures/CustomMetadata.swift | 2 +- .../DataStructures/OverridePlayerAsset.swift | 24 ++++ ios/Video/DataStructures/TextTrack.swift | 2 +- ios/Video/DataStructures/VideoSource.swift | 2 +- ios/Video/RCTVideo.swift | 22 ++- ios/Video/RNVAVPlayerPlugin.swift | 8 ++ ios/Video/ReactNativeVideoManager.swift | 12 ++ 10 files changed, 196 insertions(+), 81 deletions(-) create mode 100644 ios/Video/DataStructures/OverridePlayerAsset.swift diff --git a/docs/pages/other/plugin.md b/docs/pages/other/plugin.md index 68bcc6e9ed..1ae8ddc8da 100644 --- a/docs/pages/other/plugin.md +++ b/docs/pages/other/plugin.md @@ -209,29 +209,80 @@ class MyAVPlayerAnalyticsPlugin: RNVAVPlayerPlugin { override func onInstanceRemoved(id: String, player: AVPlayer) { // Handle AVPlayer removal with type-safe access } + + /// Optionally override the asset used by the player before playback starts + override func overridePlayerAsset(source: VideoSource, asset: AVAsset) async -> OverridePlayerAssetResult? { + // Return a modified asset or nil to use the default + return nil + } } ``` -The `RNVPlugin` class defines two methods: +The `RNVAVPlayerPlugin` class defines several extension points: ```swift /** - * Function called when a new player is created + * Function called when a new AVPlayer instance is created * @param id: a random string identifying the player - * @param player: the instantiated player reference + * @param player: the instantiated AVPlayer */ -open func onInstanceCreated(id: String, player: Any) { /* no-op */ } +open func onInstanceCreated(id: String, player: AVPlayer) { /* no-op */ } /** - * Function called when a player should be destroyed - * when this callback is called, the plugin shall free all - * resources and release all reference to Player object + * Function called when an AVPlayer instance is being removed * @param id: a random string identifying the player - * @param player: the player to release + * @param player: the AVPlayer to release + */ +open func onInstanceRemoved(id: String, player: AVPlayer) { /* no-op */ } + +/** + * Optionally override the asset used by the player before playback starts. + * Allows you to modify or replace the AVAsset before it is used to create the AVPlayerItem. + * Return nil to use the default asset. + * + * @param source: The VideoSource describing the video (uri, type, headers, etc.) + * @param asset: The AVAsset prepared by the player + * @return: OverridePlayerAssetResult if you want to override, or nil to use the default */ -open func onInstanceRemoved(id: String, player: Any) { /* no-op */ } +open func overridePlayerAsset(source: VideoSource, asset: AVAsset) async -> OverridePlayerAssetResult? { nil } +``` + +##### `OverridePlayerAssetResult` and `OverridePlayerAssetType` + +To override the asset, return an `OverridePlayerAssetResult`: + +```swift +public struct OverridePlayerAssetResult { + public let type: OverridePlayerAssetType + public let asset: AVAsset + + public init(type: OverridePlayerAssetType, asset: AVAsset) { + self.type = type + self.asset = asset + } +} + +public enum OverridePlayerAssetType { + case partial // Return a partially modified asset; will go through the default prepare process + case full // Return a fully modified asset; will skip the default prepare process +} ``` +- Use `.partial` if you want the asset to continue through the player's normal preparation (e.g., for text tracks or metadata injection). +- Use `.full` if you want to provide a fully prepared asset that will be used as-is for playback. + +**Example:** + +```swift +override func overridePlayerAsset(source: VideoSource, asset: AVAsset) async -> OverridePlayerAssetResult? { + // Example: Replace the asset URL + let newAsset = AVAsset(url: URL(string: "https://example.com/override.mp4")!) + return Result(type: .full, asset: newAsset) +} +``` + +> Only one plugin can override the player asset at a time. If multiple plugins implement this, only the first will be used. + ### 3. Register the Plugin To register the plugin in `react-native-video`, call: diff --git a/examples/expo/ios/Podfile.lock b/examples/expo/ios/Podfile.lock index 0b4e0fa2bf..29470433ef 100644 --- a/examples/expo/ios/Podfile.lock +++ b/examples/expo/ios/Podfile.lock @@ -1,19 +1,19 @@ PODS: - boost (1.84.0) - DoubleConversion (1.1.6) - - EXConstants (17.0.3): + - EXConstants (17.0.8): - ExpoModulesCore - - Expo (52.0.7): + - Expo (52.0.46): - ExpoModulesCore - - ExpoAsset (11.0.1): + - ExpoAsset (11.0.5): - ExpoModulesCore - - ExpoFileSystem (18.0.3): + - ExpoFileSystem (18.0.12): - ExpoModulesCore - - ExpoFont (13.0.1): + - ExpoFont (13.0.4): - ExpoModulesCore - - ExpoKeepAwake (14.0.1): + - ExpoKeepAwake (14.0.3): - ExpoModulesCore - - ExpoModulesCore (2.0.3): + - ExpoModulesCore (2.2.3): - DoubleConversion - glog - hermes-engine @@ -36,7 +36,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - ExpoSplashScreen (0.29.11): + - ExpoSplashScreen (0.29.24): - ExpoModulesCore - FBLazyVector (0.76.2-0) - fmt (9.1.0) @@ -1278,7 +1278,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-video (6.10.0): + - react-native-video (6.12.0): - DoubleConversion - glog - hermes-engine @@ -1291,7 +1291,7 @@ PODS: - React-featureflags - React-graphics - React-ImageManager - - react-native-video/Video (= 6.10.0) + - react-native-video/Video (= 6.12.0) - React-NativeModulesApple - React-RCTFabric - React-rendererdebug @@ -1322,7 +1322,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-video/Video (6.10.0): + - react-native-video/Video (6.12.0): - DoubleConversion - glog - hermes-engine @@ -1851,80 +1851,80 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: ad4d3a971e4f0b520a127c02c8c3f98b43394c15 DoubleConversion: 9a8708fb8299350a38a3425761aaea31263b7443 - EXConstants: dd2fe64c6cdb1383b694c309a63028a8e9f2be6d - Expo: 46cbe74ce0d0f4a4d7b726e90693eb8dfcec6de0 - ExpoAsset: 8138f2a9ec55ae1ad7c3871448379f7d97692d15 - ExpoFileSystem: cc31b7a48031ab565f9eb5c2b61aa08d774a271a - ExpoFont: 7522d869d84ee2ee8093ee997fef5b86f85d856b - ExpoKeepAwake: 783e68647b969b210a786047c3daa7b753dcac1f - ExpoModulesCore: 2d1df04dc27f91d8b383c0ec7f1d121e3d9b7f68 - ExpoSplashScreen: 26cee50e9e95572baf87cd3a8ccaf2ffc3856422 + EXConstants: fcfc75800824ac2d5c592b5bc74130bad17b146b + Expo: a6ff273c618506b12129a0d06f2a08201bfbcf43 + ExpoAsset: 48386d40d53a8c1738929b3ed509bcad595b5516 + ExpoFileSystem: 42d363d3b96f9afab980dcef60d5657a4443c655 + ExpoFont: f354e926f8feae5e831ec8087f36652b44a0b188 + ExpoKeepAwake: b0171a73665bfcefcfcc311742a72a956e6aa680 + ExpoModulesCore: 15f60d00e33ca0cea27147543877ca6e70c42ef5 + ExpoSplashScreen: 399ee9f85b6c8a61b965e13a1ecff8384db591c2 FBLazyVector: 6bbb8b88508d32cdf0ffb7ceddedf6aca79eff14 fmt: a150d37a5595336f19c91e6f1db7fcf17d5eb99f glog: 9ab333e271c177cecb6362fe27832c88667cb7e9 hermes-engine: 7bc33a9a81e6a58b3bc3426105ecd59981ac730e - RCT-Folly: 2512380c804686400e7f60b9e8b60525df4a6191 + RCT-Folly: 839d900d8e4d2b726f3277dd085d97c8904f9eac RCTDeprecation: b90bc991c207fa777f3d4f0d7f36d7473e62319a RCTRequired: c8fda281396e4e5cf96e3ab216c05986fb37ca3e RCTTypeSafety: 28a29fb79051f043943135d2e4403e77d3b7ec21 React: dade949d9edcee964714fe90a2892beef3d3a54b React-callinvoker: a6517e487e1645d2bc627be8d86b1f8dd5278bb5 - React-Core: 7010fbd0ed122476aa2cf7aa9619ec9ffb73c551 - React-CoreModules: 64eea3d14266e79f8362da71623a5157a7a6c41f - React-cxxreact: 01f36b93aa3c923f7607c50a43cc2b5a06129f07 + React-Core: 3b252d6ed5c2b9e50ed245f95aa4de1f6b5849ab + React-CoreModules: 5df4a1fa73a93ca46c33f22148b4f4693b64364e + React-cxxreact: e8b2b06dfbb9d646d9b45a29c3fc67f230e76fa9 React-debug: 25139c51b9fda09bb1745952c81cea9f46225d6a - React-defaultsnativemodule: 3805189f0d7bce05b3650d364676cf9b49e75eab - React-domnativemodule: abb803da2e62c1acbae61d277fd044fe77f6a9af - React-Fabric: c8d1c4207248fbaf9c046d92d81bab67d4694122 - React-FabricComponents: 7147628a2fec488771369e0a0dead89ad9649e0e - React-FabricImage: f814221b18a7d560baaa5fb807895c4c345036be + React-defaultsnativemodule: 6a217b48ce4eb487e7f0a9697ba6513a274cdbdc + React-domnativemodule: 64d0ffb108dc00a1275338afdfb78766fbd799d0 + React-Fabric: cb88e6fe3e97be05b425413dbba4fb6cab8e9b34 + React-FabricComponents: c5a6e9c81f9a64521ac8abf9ca2f2532e1398b86 + React-FabricImage: 724ae12ceb53b5f01dfc03d0acd63e392487713f React-featureflags: b684bbc804b9c85754fd009167a527a6eadbd9d5 - React-featureflagsnativemodule: 8b29cc7a659d17d0072924a83de9cf9a62a4d147 - React-graphics: 4d7d1fa0ea456c398de7b1f40f708885bccec2e8 - React-hermes: 981bd8b0f89a50a8fd06079bbdc4b11003135021 - React-idlecallbacksnativemodule: 12e7223f085d9d0b82aa0a10b0691294b11c792e - React-ImageManager: 4300a7a83ee603351659dade2e61e673ae3b88d2 - React-jserrorhandler: 40984dfabb1ab0b2e21d9d21af66530d5309aad0 - React-jsi: ecb93733e8efcd240957abf32b33624ce207d1f9 - React-jsiexecutor: 7ceddf35714b4c4a95ea1f590a16d96b1d577c49 - React-jsinspector: fe8b01493fb3cb8c6b946cb24c3e83e5d774fb1e - React-jsitracing: 47a37e9a623aadd867574d8143fa314df57f1f49 - React-logger: e928ead07d07fbe16f5bebc7ee004bfb7bf731d5 - React-Mapbuffer: 08b067fc26d19868b0a1fea3586d92afff4e8e4f - React-microtasksnativemodule: 309d3c6653f4ffcb8a3956604e07ab19b006ef82 - react-native-video: 25ecb15d6ac0f97e2d863cbe317c70bd6e8f1a6e - react-native-video-plugin-sample: 1f6b1dda047cdf07f6cd7e730dc92c1349656b1a + React-featureflagsnativemodule: 46d989a753848048218ea29a30c081274c42e661 + React-graphics: accc162849d86b3a8607ce80dd49afc129f35f66 + React-hermes: 2702dda9e6550e7bfcd6d11ed2dd5ac79ee824a7 + React-idlecallbacksnativemodule: c271aad98748669cf8a045b0559a0d3fd67013d2 + React-ImageManager: 6eb6ea557f32850680896907964fc0a8515721db + React-jserrorhandler: 6236637935a035413e9ada45c7a08a2a58fa7212 + React-jsi: a18137983c0690f3e29882128aa7daf624d2fdac + React-jsiexecutor: 75909d0eeac14133f33a0446b776388591e1c7c7 + React-jsinspector: db3334448e910b60b59cd1809dcf747970525077 + React-jsitracing: 545fc999f9db67b157323f439ff964caccae2c48 + React-logger: c1804a09cb1c3486fcd9c2b4753ba7b5b9ae1c38 + React-Mapbuffer: 2c717de369c66af62d34cdf52202da3951feab59 + React-microtasksnativemodule: c285dc3fa70ea3099d8ee0887aab0fc53f3ef791 + react-native-video: b18f2dda48b6424cb8d4d6168821223bbf9eb38a + react-native-video-plugin-sample: 79af31831cddfa916eb264d89b006fdcf227a8f5 React-nativeconfig: e90353e1c5ab46843222e25d529d342c174184f4 - React-NativeModulesApple: 5f46e9be9fbff00c706cb31a91753975e99f86a1 - React-perflogger: a629f2ce2eeab290cfc851a015c5d804a8b36f20 - React-performancetimeline: 09eaa1261ce7b3d51dcd8001f922c7b7f018957c + React-NativeModulesApple: de38a9d30fafd773bc72e8b078d57de2dfccae7f + React-perflogger: 63dd38ee6f413be0aa176de9f6efc65819e289ca + React-performancetimeline: 450a56eff8fbe3de667f26d1857f3bd150e037a7 React-RCTActionSheet: 0622ace751a55109844ff5d6239c83ae36f193d6 - React-RCTAnimation: 8651b17476b17d97d7f0fad16d9020f9b1724226 - React-RCTAppDelegate: 09147856b3e6c55f45afc32f485697b9c7097830 - React-RCTBlob: 4ce6e176c0b0999b6ae907537c665dec382095ca - React-RCTFabric: 6d6d776ec2bb746a599ca113da4c9e5e30519175 - React-RCTImage: 1ba821a26daf160f596266f78b084df38bad43bf - React-RCTLinking: b913efa56e459a7c9bd8575ea35fffcd43d2b226 - React-RCTNetwork: ff7b971a79b35407aa7fc84d0d34d4bf87977832 - React-RCTSettings: a40d2c591ea4b3ebeb0d5fca84cef83bf3a808b8 - React-RCTText: 54e4c01e54f2b323494950100b3f96a44c8d756a - React-RCTVibration: 415d77ace6572495a7fabc0013794eb232263f61 + React-RCTAnimation: 90e804d3918a97910c94732f23f1c3d3ac43d1ff + React-RCTAppDelegate: 7503e65922a7de2dcb04c7ad9dba8d18c0c1084b + React-RCTBlob: e0ac1d709fab205330a6a4ff3430b19358fe2aa4 + React-RCTFabric: a308ebf92877e49196d81568a0098bc5a54b0cc2 + React-RCTImage: 3ebf257e04f4e0d366e8d11e9ee0c9fb1d5318e4 + React-RCTLinking: 86a8f8fb5169e0499d8ff9f17b466103a9c7344f + React-RCTNetwork: 9d8dae79abf6281174aabdcd5cb952b125a19834 + React-RCTSettings: 26320c05739c8dc553e87f8195efb51fa1a4f4d1 + React-RCTText: 1b3aafa95d0b8f5546ea8b11f218b97a6c50e6ac + React-RCTVibration: b4b8c7ca82031b6a628d5197db32123786269f83 React-rendererconsistency: abd0c952e6447de1851995630d388adb756c5490 - React-rendererdebug: 931afa3e69cad5617d70fc8d22a51b8f0b8962ab + React-rendererdebug: d2cee4431aae3b94a369922950314bdd11ed4e36 React-rncore: 4fa27b1f5758f1e43daecaca33828b6724ff975e - React-RuntimeApple: bc87436ff646bc38b3b40b5c1b64b3292083b5d6 - React-RuntimeCore: 4588a5b4bf68324088d3aba640c7aecda1f8ccf7 + React-RuntimeApple: 4400921398bc7f37dea96b38408f75fa79a4e087 + React-RuntimeCore: f5d8891ca6c8fd0ea1ce97e1e742f674c6e3f3de React-runtimeexecutor: 369f2c0dd457ecc25d8aca20717b8b7c655fea30 - React-RuntimeHermes: fa0dbe3e215052f6f2a64961a81292a7d8a29cc1 - React-runtimescheduler: 0c3791a76e2c851a6c0387549f6288cb9dd6e3d6 + React-RuntimeHermes: f36d89a2b787488c5c52b56f6e390ea6daad8177 + React-runtimescheduler: d767ba9dbcc56926ef8e33d9a79ffc73b1f21216 React-timing: d1b4d886b3ad54cb7d7902551d6f326de16b13d3 - React-utils: 5b35e0df4b1e5414ccb7aad3295de9e422d19265 - ReactCodegen: 64ba3fd7eb98692015dbd29ca0ca38ea2c5f92c6 - ReactCommon: 8676c2ee5eb8c937ea33df7c91f42d4cd9e95e79 - RNCPicker: f963e01f78e546a93b98aa201501713dbda14e94 + React-utils: b5263797499f58d23f8cdf84a43947fa935144a5 + ReactCodegen: be823df68a7af474545f249dab02cd94893917e8 + ReactCommon: ccfca2ddb61b208d3a9bfbbfc87233656d65a4d8 + RNCPicker: 0e683a85cae99a8a739e97a49f269a4630de0a01 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - Yoga: aaa386a35c44b356306dafca9e8afd81877fb7ed + Yoga: 748384d41d07db337d313e0f37eec81c8c0ae46f PODFILE CHECKSUM: 90d803972b4acfc1d2bb6dbe7b0713677a2ff655 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/ios/Video/DataStructures/AdParams.swift b/ios/Video/DataStructures/AdParams.swift index be97fe0a62..26c0a25e94 100644 --- a/ios/Video/DataStructures/AdParams.swift +++ b/ios/Video/DataStructures/AdParams.swift @@ -1,4 +1,4 @@ -struct AdParams { +public struct AdParams { let adTagUrl: String? let adLanguage: String? diff --git a/ios/Video/DataStructures/CustomMetadata.swift b/ios/Video/DataStructures/CustomMetadata.swift index 9526d68017..8a38abf651 100644 --- a/ios/Video/DataStructures/CustomMetadata.swift +++ b/ios/Video/DataStructures/CustomMetadata.swift @@ -1,4 +1,4 @@ -struct CustomMetadata { +public struct CustomMetadata { let title: String? let subtitle: String? let artist: String? diff --git a/ios/Video/DataStructures/OverridePlayerAsset.swift b/ios/Video/DataStructures/OverridePlayerAsset.swift new file mode 100644 index 0000000000..07504e0d9b --- /dev/null +++ b/ios/Video/DataStructures/OverridePlayerAsset.swift @@ -0,0 +1,24 @@ +import AVFoundation + +// MARK: - OverridePlayerAssetType + +public enum OverridePlayerAssetType { + // Return partially modified asset, that will go through the default prepare process + case partial + // Return fully modified asset, that will skip the default prepare process + case full +} + +public typealias OverridePlayerAsset = (VideoSource, AVAsset) async -> OverridePlayerAssetResult + +// MARK: - OverridePlayerAssetResult + +public struct OverridePlayerAssetResult { + public let type: OverridePlayerAssetType + public let asset: AVAsset + + public init(type: OverridePlayerAssetType, asset: AVAsset) { + self.type = type + self.asset = asset + } +} diff --git a/ios/Video/DataStructures/TextTrack.swift b/ios/Video/DataStructures/TextTrack.swift index 4c186b288c..f25bbd3160 100644 --- a/ios/Video/DataStructures/TextTrack.swift +++ b/ios/Video/DataStructures/TextTrack.swift @@ -1,4 +1,4 @@ -struct TextTrack { +public struct TextTrack { let type: String let language: String let title: String diff --git a/ios/Video/DataStructures/VideoSource.swift b/ios/Video/DataStructures/VideoSource.swift index 1f36714988..0a5e890c34 100644 --- a/ios/Video/DataStructures/VideoSource.swift +++ b/ios/Video/DataStructures/VideoSource.swift @@ -1,4 +1,4 @@ -struct VideoSource { +public struct VideoSource { let type: String? let uri: String? let isNetwork: Bool diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 369d465f5f..94f1e8f090 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -493,7 +493,19 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH ]) if let uri = source.uri, uri.starts(with: "ph://") { - let photoAsset = await RCTVideoUtils.preparePHAsset(uri: uri) + guard let photoAsset = await RCTVideoUtils.preparePHAsset(uri: uri) else { + DebugLog("Could not load asset '\(String(describing: _source))'") + throw NSError(domain: "", code: 0, userInfo: nil) + } + + if let overridePlayerAsset = await ReactNativeVideoManager.shared.overridePlayerAsset(source: source, asset: photoAsset) { + if overridePlayerAsset.type == .full { + return AVPlayerItem(asset: overridePlayerAsset.asset) + } + + return await playerItemPrepareText(source: source, asset: overridePlayerAsset.asset, assetOptions: nil, uri: source.uri ?? "") + } + return await playerItemPrepareText(source: source, asset: photoAsset, assetOptions: nil, uri: source.uri ?? "") } @@ -530,6 +542,14 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH ) } + if let overridePlayerAsset = await ReactNativeVideoManager.shared.overridePlayerAsset(source: source, asset: asset) { + if overridePlayerAsset.type == .full { + return AVPlayerItem(asset: overridePlayerAsset.asset) + } + + return await playerItemPrepareText(source: source, asset: overridePlayerAsset.asset, assetOptions: assetOptions, uri: source.uri ?? "") + } + return await playerItemPrepareText(source: source, asset: asset, assetOptions: assetOptions, uri: source.uri ?? "") } diff --git a/ios/Video/RNVAVPlayerPlugin.swift b/ios/Video/RNVAVPlayerPlugin.swift index 1e62d55d91..c6c528e8d8 100644 --- a/ios/Video/RNVAVPlayerPlugin.swift +++ b/ios/Video/RNVAVPlayerPlugin.swift @@ -36,6 +36,14 @@ open class RNVAVPlayerPlugin: RNVPlugin { */ open func onInstanceRemoved(id _: String, player _: AVPlayer) { /* no-op */ } + /** + * Function called when creating a new AVPlayerItem + * @param source: The VideoSource describing the video (uri, type, headers, etc.) + * @param asset: The AVAsset prepared by the player + * @return: OverridePlayerAssetResult if you want to override, or nil if you don't + */ + open func overridePlayerAsset(source _: VideoSource, asset _: AVAsset) async -> OverridePlayerAssetResult? { nil } + // MARK: - RNVPlugin methods override public func onInstanceCreated(id: String, player: Any) { diff --git a/ios/Video/ReactNativeVideoManager.swift b/ios/Video/ReactNativeVideoManager.swift index d5efbcadb4..61c6a7ce99 100644 --- a/ios/Video/ReactNativeVideoManager.swift +++ b/ios/Video/ReactNativeVideoManager.swift @@ -3,6 +3,7 @@ // react-native-video // +import AVFoundation import Foundation public class ReactNativeVideoManager: RNVPlugin { @@ -67,6 +68,17 @@ public class ReactNativeVideoManager: RNVPlugin { return customDRMManager?.1 ?? DRMManager() } + public func overridePlayerAsset(source: VideoSource, asset: AVAsset) async -> OverridePlayerAssetResult? { + for plugin in pluginList { + if let avpPlugin = plugin as? RNVAVPlayerPlugin, + let overridePlayerAsset = await avpPlugin.overridePlayerAsset(source: source, asset: asset) { + return overridePlayerAsset + } + } + + return nil + } + // MARK: - Helper methods func maybeRegisterAVPlayerPlugin(plugin: RNVPlugin) { From 735e35a2ea4be642f5dcd6a62b7ec42ed69b3c64 Mon Sep 17 00:00:00 2001 From: Krzysztof Moch Date: Fri, 18 Apr 2025 18:08:14 +0200 Subject: [PATCH 05/47] chore: release v6.13.0 --- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f3a89842a..59d9a32d04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ +# [6.13.0](https://github.com/TheWidlarzGroup/react-native-video/compare/v6.12.0...v6.13.0) (2025-04-18) + + +### Bug Fixes + +* **macCatalyst:** allowsVideoFrameAnalysis not available in catalyst until 18.0 ([#4515](https://github.com/TheWidlarzGroup/react-native-video/issues/4515)) ([b17c319](https://github.com/TheWidlarzGroup/react-native-video/commit/b17c319c32ad8080ad911ec4be7fa02bc4a6d6ee)) +* **tvos:** build ([#4511](https://github.com/TheWidlarzGroup/react-native-video/issues/4511)) ([4034046](https://github.com/TheWidlarzGroup/react-native-video/commit/40340467d7df2ad606f25606041630c489c385f6)) + + +### Features + +* **ios:** add `overridePlayerAsset` to `AVPlayerPlugin` ([#4522](https://github.com/TheWidlarzGroup/react-native-video/issues/4522)) ([b1b3db3](https://github.com/TheWidlarzGroup/react-native-video/commit/b1b3db301097e82c8d449c08f7d334a2eaeac0ea)) + # [6.12.0](https://github.com/TheWidlarzGroup/react-native-video/compare/v6.11.0...v6.12.0) (2025-04-06) diff --git a/package.json b/package.json index 7293def574..806d535851 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-video", - "version": "6.12.0", + "version": "6.13.0", "description": "A