update trailer extraction

This commit is contained in:
tapframe 2026-03-13 08:40:52 +05:30
parent fb0805324d
commit 8f371215e9
16 changed files with 2886 additions and 692 deletions

View file

@ -33,7 +33,15 @@
"audio"
],
"LSSupportsOpeningDocumentsInPlace": true,
"UIFileSharingEnabled": true
"UIFileSharingEnabled": true,
"LSApplicationQueriesSchemes": [
"vlc",
"vlc-x-callback",
"infuse",
"outplayer",
"open-vidhub",
"livecontainer"
]
},
"bundleIdentifier": "com.nuvio.hub",
"associatedDomains": [],

View file

@ -7,5 +7,7 @@
<key>NSExtensionPointIdentifier</key>
<string>com.apple.widgetkit-extension</string>
</dict>
<key>RCTNewArchEnabled</key>
<true/>
</dict>
</plist>

View file

@ -7,147 +7,77 @@
objects = {
/* Begin PBXBuildFile section */
0FFC28FB1FEA74CCFA112268 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 49055D6E250FAFA21141FE49 /* PrivacyInfo.xcprivacy */; };
0512F937B36046F581AF6A55 /* View+applyIfPresent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65BDE00720574797863FA748 /* View+applyIfPresent.swift */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
2AA769395C1242F225F875AF /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E007C0BAC8C453623E81663 /* ExpoModulesProvider.swift */; };
24FC08E856045AC6A95D753A /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 495FF0F91AFAF8A860B9D485 /* PrivacyInfo.xcprivacy */; };
357A8847EA6B42B792A617D4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 348452A2DFC344C2BC70A974 /* Assets.xcassets */; };
3A4B91216E8D486E9000CD6C /* Date+toTimerInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A20BAB83F6E40D880665E14 /* Date+toTimerInterval.swift */; };
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; };
730F1CDE2F24B27100EF7E51 /* Color+hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8034143A77A946B5A793F967 /* Color+hex.swift */; };
730F1CDF2F24B27100EF7E51 /* Date+toTimerInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A48D8A298DD48928E8D0A02 /* Date+toTimerInterval.swift */; };
730F1CE02F24B27100EF7E51 /* Image+dynamic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26957CDD392E4E9390811D0D /* Image+dynamic.swift */; };
730F1CE12F24B27100EF7E51 /* LiveActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F448294A36E433E924078C1 /* LiveActivityView.swift */; };
730F1CE22F24B27100EF7E51 /* LiveActivityWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD48662BB71E4C9C9E340289 /* LiveActivityWidget.swift */; };
730F1CE32F24B27100EF7E51 /* LiveActivityWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = A83D742B36224176A0AB3B25 /* LiveActivityWidgetBundle.swift */; };
730F1CE42F24B27100EF7E51 /* View+applyIfPresent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 324373F393774A9CA40DE22E /* View+applyIfPresent.swift */; };
730F1CE52F24B27100EF7E51 /* View+applyWidgetURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373D1473F5A74CBC9DBD108B /* View+applyWidgetURL.swift */; };
730F1CE62F24B27100EF7E51 /* ViewHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3396D68881EF486E99FD480A /* ViewHelpers.swift */; };
730F1CE72F24B27100EF7E51 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0F1D0037D1F24E60BDB57628 /* Assets.xcassets */; };
9FBA88F42E86ECD700892850 /* KSPlayerViewManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBA88F32E86ECD700892850 /* KSPlayerViewManager.swift */; };
9FBA88F52E86ECD700892850 /* KSPlayerModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBA88F12E86ECD700892850 /* KSPlayerModule.swift */; };
9FBA88F62E86ECD700892850 /* KSPlayerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FBA88F02E86ECD700892850 /* KSPlayerManager.m */; };
9FBA88F72E86ECD700892850 /* KSPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBA88F22E86ECD700892850 /* KSPlayerView.swift */; };
A0892AA96024D9EF7CA87A8A /* libPods-Nuvio.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 349BFD3B214640DED8541999 /* libPods-Nuvio.a */; };
4F1568831D134295A56C0263 /* LiveActivityWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B775F1944CB4E2EB043DB23 /* LiveActivityWidgetBundle.swift */; };
7069406A1E324608A86CC6C9 /* ViewHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D296491CC4F4406182E2A2BC /* ViewHelpers.swift */; };
94F843501BFD40C384CBB500 /* View+applyWidgetURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0AC8A8984F4495492E13EFD /* View+applyWidgetURL.swift */; };
9BE191900481495083B7ECC1 /* Color+hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = B36264437D84461BBC2BB706 /* Color+hex.swift */; };
B3BB1BBDA8C742DC9A32EACF /* Image+dynamic.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03926C841844CC38C468345 /* Image+dynamic.swift */; };
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; };
BD4FE0C298E078EA08432C75 /* libPods-Nuvio.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7206543A5AAE2788326BFBC7 /* libPods-Nuvio.a */; };
D102CD3E3CCC4B83AC0F4DBE /* LiveActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3182DD61A7C04BD383DBB4B0 /* LiveActivityView.swift */; };
DEB0D346CF964886AD06A268 /* LiveActivityWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C77B925A48A4711B3B59C6A /* LiveActivityWidget.swift */; };
F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11748412D0307B40044C1D9 /* AppDelegate.swift */; };
F285A1620F5847BA863124AF /* LiveActivity.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = EF8716173E0148BD82B233B7 /* LiveActivity.appex */; };
797799D4F9144A9E8D2AB90D /* LiveActivity.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 49DDF70A2BBD4320BBD94B1B /* LiveActivity.appex */; };
F1924CDEBB5847D8A1AFD925 /* LiveActivity.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 37E2BF6107484CD098F81560 /* LiveActivity.appex */; };
F4B2944DA5D06650F3B40F2A /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B2F28305B8518622853186 /* ExpoModulesProvider.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
55A0DD628D7F4F4F88B4A001 /* PBXContainerItemProxy */ = {
DBC6C07FF91444EEAB5F4C66 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 0EA489F2BF6143F1BA7B8485;
remoteInfo = LiveActivity;
};
7A41A1F529994F0C8801F1A5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
proxyType = 1;
remoteGlobalIDString = EFB756D21A05453EA489278C;
remoteGlobalIDString = BB0FA6CBF7FD404994DB1E52;
remoteInfo = LiveActivity;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
13CD9594FB5C4FE4A6794089 /* Embed Foundation Extensions */ = {
8773BA7C99F44506A4A25842 /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
797799D4F9144A9E8D2AB90D /* LiveActivity.appex in Embed Foundation Extensions */,
F1924CDEBB5847D8A1AFD925 /* LiveActivity.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
3447F08B99D9427E99FEE18E /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
F285A1620F5847BA863124AF /* LiveActivity.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
571AD3FB23F14FC7BE6A1E44 /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
BDCAC5D772944755921F3BCF /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
F1058FE7710A45FABC0689A7 /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
CC1B793274FE428D8531E950 /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
name = "Embed Foundation Extensions";
dstPath = "";
dstSubfolderSpec = 13;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
0DFF64A670930CED5EA4DF3A /* Pods-Nuvio.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Nuvio.release.xcconfig"; path = "Target Support Files/Pods-Nuvio/Pods-Nuvio.release.xcconfig"; sourceTree = "<group>"; };
0E13CE4BDE2F4555806AE753 /* Info.plist */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0F1D0037D1F24E60BDB57628 /* Assets.xcassets */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
0E5AC167979645D9ADB47923 /* LiveActivity.entitlements */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = LiveActivity.entitlements; path = LiveActivity.entitlements; sourceTree = "<group>"; };
13B07F961A680F5B00A75B9A /* Nuvio.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Nuvio.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Nuvio/Images.xcassets; sourceTree = "<group>"; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Nuvio/Info.plist; sourceTree = "<group>"; };
26957CDD392E4E9390811D0D /* Image+dynamic.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; path = "Image+dynamic.swift"; sourceTree = "<group>"; };
2DE29A8A87D24662BEFFF849 /* LiveActivity.entitlements */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; path = LiveActivity.entitlements; sourceTree = "<group>"; };
2F448294A36E433E924078C1 /* LiveActivityView.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; path = LiveActivityView.swift; sourceTree = "<group>"; };
324373F393774A9CA40DE22E /* View+applyIfPresent.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; path = "View+applyIfPresent.swift"; sourceTree = "<group>"; };
3396D68881EF486E99FD480A /* ViewHelpers.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; path = ViewHelpers.swift; sourceTree = "<group>"; };
349BFD3B214640DED8541999 /* libPods-Nuvio.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Nuvio.a"; sourceTree = BUILT_PRODUCTS_DIR; };
373D1473F5A74CBC9DBD108B /* View+applyWidgetURL.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; path = "View+applyWidgetURL.swift"; sourceTree = "<group>"; };
3A48D8A298DD48928E8D0A02 /* Date+toTimerInterval.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; path = "Date+toTimerInterval.swift"; sourceTree = "<group>"; };
49055D6E250FAFA21141FE49 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = Nuvio/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
6E007C0BAC8C453623E81663 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-Nuvio/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
730F1CE82F24B29C00EF7E51 /* LiveActivityDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LiveActivityDebug.entitlements; sourceTree = "<group>"; };
73BB213C2E9EEAC700EC03F8 /* NuvioRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = NuvioRelease.entitlements; path = Nuvio/NuvioRelease.entitlements; sourceTree = "<group>"; };
8034143A77A946B5A793F967 /* Color+hex.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; path = "Color+hex.swift"; sourceTree = "<group>"; };
9FBA88F02E86ECD700892850 /* KSPlayerManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ../KSPlayer/RNBridge/KSPlayerManager.m; sourceTree = "<group>"; };
9FBA88F12E86ECD700892850 /* KSPlayerModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ../KSPlayer/RNBridge/KSPlayerModule.swift; sourceTree = "<group>"; };
9FBA88F22E86ECD700892850 /* KSPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ../KSPlayer/RNBridge/KSPlayerView.swift; sourceTree = "<group>"; };
9FBA88F32E86ECD700892850 /* KSPlayerViewManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ../KSPlayer/RNBridge/KSPlayerViewManager.swift; sourceTree = "<group>"; };
A83D742B36224176A0AB3B25 /* LiveActivityWidgetBundle.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; path = LiveActivityWidgetBundle.swift; sourceTree = "<group>"; };
1B775F1944CB4E2EB043DB23 /* LiveActivityWidgetBundle.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = LiveActivityWidgetBundle.swift; path = LiveActivityWidgetBundle.swift; sourceTree = "<group>"; };
3182DD61A7C04BD383DBB4B0 /* LiveActivityView.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = LiveActivityView.swift; path = LiveActivityView.swift; sourceTree = "<group>"; };
348452A2DFC344C2BC70A974 /* Assets.xcassets */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Assets.xcassets; sourceTree = "<group>"; };
37E2BF6107484CD098F81560 /* LiveActivity.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = undefined; name = LiveActivity.appex; path = LiveActivity.appex; sourceTree = BUILT_PRODUCTS_DIR; };
3A20BAB83F6E40D880665E14 /* Date+toTimerInterval.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = "Date+toTimerInterval.swift"; path = "Date+toTimerInterval.swift"; sourceTree = "<group>"; };
44393AD0ABDBAED5B40D2E49 /* Pods-Nuvio.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Nuvio.release.xcconfig"; path = "Target Support Files/Pods-Nuvio/Pods-Nuvio.release.xcconfig"; sourceTree = "<group>"; };
495FF0F91AFAF8A860B9D485 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Nuvio/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
65BDE00720574797863FA748 /* View+applyIfPresent.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = "View+applyIfPresent.swift"; path = "View+applyIfPresent.swift"; sourceTree = "<group>"; };
7206543A5AAE2788326BFBC7 /* libPods-Nuvio.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Nuvio.a"; sourceTree = BUILT_PRODUCTS_DIR; };
7EC866223BB0FA7280D468BF /* Pods-Nuvio.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Nuvio.debug.xcconfig"; path = "Target Support Files/Pods-Nuvio/Pods-Nuvio.debug.xcconfig"; sourceTree = "<group>"; };
9C77B925A48A4711B3B59C6A /* LiveActivityWidget.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = LiveActivityWidget.swift; path = LiveActivityWidget.swift; sourceTree = "<group>"; };
A0AC8A8984F4495492E13EFD /* View+applyWidgetURL.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = "View+applyWidgetURL.swift"; path = "View+applyWidgetURL.swift"; sourceTree = "<group>"; };
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = Nuvio/SplashScreen.storyboard; sourceTree = "<group>"; };
AD48662BB71E4C9C9E340289 /* LiveActivityWidget.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; path = LiveActivityWidget.swift; sourceTree = "<group>"; };
B36264437D84461BBC2BB706 /* Color+hex.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = "Color+hex.swift"; path = "Color+hex.swift"; sourceTree = "<group>"; };
B9B2F28305B8518622853186 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-Nuvio/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
DAD634845937EAF8D64F20FC /* Pods-Nuvio.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Nuvio.debug.xcconfig"; path = "Target Support Files/Pods-Nuvio/Pods-Nuvio.debug.xcconfig"; sourceTree = "<group>"; };
D296491CC4F4406182E2A2BC /* ViewHelpers.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = ViewHelpers.swift; path = ViewHelpers.swift; sourceTree = "<group>"; };
E93B94AE2F7240878D655B2F /* Info.plist */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = text.plist.xml; name = Info.plist; path = Info.plist; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
EF8716173E0148BD82B233B7 /* LiveActivity.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; fileEncoding = 9; includeInIndex = 0; path = LiveActivity.appex; sourceTree = BUILT_PRODUCTS_DIR; };
F03926C841844CC38C468345 /* Image+dynamic.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = "Image+dynamic.swift"; path = "Image+dynamic.swift"; sourceTree = "<group>"; };
F11748412D0307B40044C1D9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = Nuvio/AppDelegate.swift; sourceTree = "<group>"; };
F11748442D0722820044C1D9 /* Nuvio-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "Nuvio-Bridging-Header.h"; path = "Nuvio/Nuvio-Bridging-Header.h"; sourceTree = "<group>"; };
49DDF70A2BBD4320BBD94B1B /* LiveActivity.appex */ = {isa = PBXFileReference; name = "LiveActivity.appex"; path = "LiveActivity.appex"; sourceTree = BUILT_PRODUCTS_DIR; fileEncoding = undefined; lastKnownFileType = undefined; explicitFileType = wrapper.app-extension; includeInIndex = 0; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -155,18 +85,11 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A0892AA96024D9EF7CA87A8A /* libPods-Nuvio.a in Frameworks */,
BD4FE0C298E078EA08432C75 /* libPods-Nuvio.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
C105694FF46449959CE16947 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
A2537B59C29048BFB082D5F3 /* Embed Foundation Extensions */ = {
2341847DAC4A41A89AA0DED2 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
@ -179,18 +102,13 @@
13B07FAE1A68108700A75B9A /* Nuvio */ = {
isa = PBXGroup;
children = (
73BB213C2E9EEAC700EC03F8 /* NuvioRelease.entitlements */,
F11748412D0307B40044C1D9 /* AppDelegate.swift */,
F11748442D0722820044C1D9 /* Nuvio-Bridging-Header.h */,
9FBA88F02E86ECD700892850 /* KSPlayerManager.m */,
9FBA88F12E86ECD700892850 /* KSPlayerModule.swift */,
9FBA88F22E86ECD700892850 /* KSPlayerView.swift */,
9FBA88F32E86ECD700892850 /* KSPlayerViewManager.swift */,
BB2F792B24A3F905000567C9 /* Supporting */,
13B07FB51A68108700A75B9A /* Images.xcassets */,
13B07FB61A68108700A75B9A /* Info.plist */,
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */,
49055D6E250FAFA21141FE49 /* PrivacyInfo.xcprivacy */,
495FF0F91AFAF8A860B9D485 /* PrivacyInfo.xcprivacy */,
);
name = Nuvio;
sourceTree = "<group>";
@ -199,23 +117,28 @@
isa = PBXGroup;
children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
349BFD3B214640DED8541999 /* libPods-Nuvio.a */,
7206543A5AAE2788326BFBC7 /* libPods-Nuvio.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
358C5C99C443A921C8EEDDC8 /* ExpoModulesProviders */ = {
isa = PBXGroup;
children = (
ECB31D9B6FF08C7E8E875650 /* Nuvio */,
);
name = ExpoModulesProviders;
sourceTree = "<group>";
};
62B088ADB2A740DAB9E343F9 /* LiveActivity */ = {
6BFD9CA52F844119ABE664C4 /* LiveActivity */ = {
isa = PBXGroup;
children = (
B36264437D84461BBC2BB706 /* Color+hex.swift */,
3A20BAB83F6E40D880665E14 /* Date+toTimerInterval.swift */,
F03926C841844CC38C468345 /* Image+dynamic.swift */,
3182DD61A7C04BD383DBB4B0 /* LiveActivityView.swift */,
9C77B925A48A4711B3B59C6A /* LiveActivityWidget.swift */,
1B775F1944CB4E2EB043DB23 /* LiveActivityWidgetBundle.swift */,
65BDE00720574797863FA748 /* View+applyIfPresent.swift */,
A0AC8A8984F4495492E13EFD /* View+applyWidgetURL.swift */,
D296491CC4F4406182E2A2BC /* ViewHelpers.swift */,
E93B94AE2F7240878D655B2F /* Info.plist */,
348452A2DFC344C2BC70A974 /* Assets.xcassets */,
0E5AC167979645D9ADB47923 /* LiveActivity.entitlements */,
);
name = LiveActivity;
path = LiveActivity;
sourceTree = "<group>";
};
@ -233,14 +156,9 @@
832341AE1AAA6A7D00B99B32 /* Libraries */,
83CBBA001A601CBA00E9B192 /* Products */,
2D16E6871FA4F8E400B85C8A /* Frameworks */,
D90A3959C97EE9926C513293 /* Pods */,
358C5C99C443A921C8EEDDC8 /* ExpoModulesProviders */,
E8C72B3DF7DB40A8896F56C9 /* LiveActivity */,
62B088ADB2A740DAB9E343F9 /* LiveActivity */,
B9F3EB198DED443D980ADFB3 /* LiveActivity */,
C05E525650E143FB85ED7622 /* LiveActivity */,
D05210A39FF14E649D77F8A8 /* LiveActivity */,
2EAF711C6AB246A0A253E404 /* LiveActivity */,
6BFD9CA52F844119ABE664C4 /* LiveActivity */,
94DA158AE0BA28C86D2B8468 /* Pods */,
FD98B8AFFD47909E52F111EF /* ExpoModulesProviders */,
);
indentWidth = 2;
sourceTree = "<group>";
@ -251,17 +169,19 @@
isa = PBXGroup;
children = (
13B07F961A680F5B00A75B9A /* Nuvio.app */,
EF8716173E0148BD82B233B7 /* LiveActivity.appex */,
49DDF70A2BBD4320BBD94B1B /* LiveActivity.appex */,
37E2BF6107484CD098F81560 /* LiveActivity.appex */,
);
name = Products;
sourceTree = "<group>";
};
B9F3EB198DED443D980ADFB3 /* LiveActivity */ = {
94DA158AE0BA28C86D2B8468 /* Pods */ = {
isa = PBXGroup;
children = (
7EC866223BB0FA7280D468BF /* Pods-Nuvio.debug.xcconfig */,
44393AD0ABDBAED5B40D2E49 /* Pods-Nuvio.release.xcconfig */,
);
path = LiveActivity;
name = Pods;
path = Pods;
sourceTree = "<group>";
};
BB2F792B24A3F905000567C9 /* Supporting */ = {
@ -273,144 +193,66 @@
path = Nuvio/Supporting;
sourceTree = "<group>";
};
C05E525650E143FB85ED7622 /* LiveActivity */ = {
E3DA69DC92F44258CBB5D7D2 /* Nuvio */ = {
isa = PBXGroup;
children = (
);
path = LiveActivity;
sourceTree = "<group>";
};
D05210A39FF14E649D77F8A8 /* LiveActivity */ = {
isa = PBXGroup;
children = (
8034143A77A946B5A793F967 /* Color+hex.swift */,
3A48D8A298DD48928E8D0A02 /* Date+toTimerInterval.swift */,
26957CDD392E4E9390811D0D /* Image+dynamic.swift */,
2F448294A36E433E924078C1 /* LiveActivityView.swift */,
AD48662BB71E4C9C9E340289 /* LiveActivityWidget.swift */,
A83D742B36224176A0AB3B25 /* LiveActivityWidgetBundle.swift */,
324373F393774A9CA40DE22E /* View+applyIfPresent.swift */,
373D1473F5A74CBC9DBD108B /* View+applyWidgetURL.swift */,
3396D68881EF486E99FD480A /* ViewHelpers.swift */,
0E13CE4BDE2F4555806AE753 /* Info.plist */,
0F1D0037D1F24E60BDB57628 /* Assets.xcassets */,
2DE29A8A87D24662BEFFF849 /* LiveActivity.entitlements */,
);
path = LiveActivity;
sourceTree = "<group>";
};
D90A3959C97EE9926C513293 /* Pods */ = {
isa = PBXGroup;
children = (
DAD634845937EAF8D64F20FC /* Pods-Nuvio.debug.xcconfig */,
0DFF64A670930CED5EA4DF3A /* Pods-Nuvio.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
E8C72B3DF7DB40A8896F56C9 /* LiveActivity */ = {
isa = PBXGroup;
children = (
730F1CE82F24B29C00EF7E51 /* LiveActivityDebug.entitlements */,
);
path = LiveActivity;
sourceTree = "<group>";
};
ECB31D9B6FF08C7E8E875650 /* Nuvio */ = {
isa = PBXGroup;
children = (
6E007C0BAC8C453623E81663 /* ExpoModulesProvider.swift */,
B9B2F28305B8518622853186 /* ExpoModulesProvider.swift */,
);
name = Nuvio;
sourceTree = "<group>";
};
2EAF711C6AB246A0A253E404 /* LiveActivity */ = {
FD98B8AFFD47909E52F111EF /* ExpoModulesProviders */ = {
isa = PBXGroup;
children = (
8034143A77A946B5A793F967 /* Color+hex.swift */,
3A48D8A298DD48928E8D0A02 /* Date+toTimerInterval.swift */,
26957CDD392E4E9390811D0D /* Image+dynamic.swift */,
2F448294A36E433E924078C1 /* LiveActivityView.swift */,
AD48662BB71E4C9C9E340289 /* LiveActivityWidget.swift */,
A83D742B36224176A0AB3B25 /* LiveActivityWidgetBundle.swift */,
324373F393774A9CA40DE22E /* View+applyIfPresent.swift */,
373D1473F5A74CBC9DBD108B /* View+applyWidgetURL.swift */,
3396D68881EF486E99FD480A /* ViewHelpers.swift */,
0E13CE4BDE2F4555806AE753 /* Info.plist */,
0F1D0037D1F24E60BDB57628 /* Assets.xcassets */,
2DE29A8A87D24662BEFFF849 /* LiveActivity.entitlements */,
E3DA69DC92F44258CBB5D7D2 /* Nuvio */,
);
name = LiveActivity;
path = LiveActivity;
name = ExpoModulesProviders;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
0EA489F2BF6143F1BA7B8485 /* LiveActivity */ = {
isa = PBXNativeTarget;
buildConfigurationList = C95083D445BA485B82D2FFBC /* Build configuration list for PBXNativeTarget "LiveActivity" */;
buildPhases = (
6E9A0429F8E74948A82DEFF5 /* Sources */,
C105694FF46449959CE16947 /* Frameworks */,
1E668E0B92C34E73AECDBE1A /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = LiveActivity;
productName = LiveActivity;
productReference = EF8716173E0148BD82B233B7 /* LiveActivity.appex */;
productType = "com.apple.product-type.app-extension";
};
13B07F861A680F5B00A75B9A /* Nuvio */ = {
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Nuvio" */;
buildPhases = (
13C7A3175A582B3D4E9F198E /* [CP] Check Pods Manifest.lock */,
99A79B70155E84EE1FB7F466 /* [Expo] Configure project */,
08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */,
B67FC995125067F91F480913 /* [Expo] Configure project */,
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
9B977D89FE30470F8C59964C /* Upload Debug Symbols to Sentry */,
E043D7E00F2210228303FC0B /* [CP] Embed Pods Frameworks */,
7F1DFB9D902E2DBC35F3FB84 /* [CP] Copy Pods Resources */,
3447F08B99D9427E99FEE18E /* Embed Foundation Extensions */,
BDCAC5D772944755921F3BCF /* Embed Foundation Extensions */,
571AD3FB23F14FC7BE6A1E44 /* Embed Foundation Extensions */,
13CD9594FB5C4FE4A6794089 /* Embed Foundation Extensions */,
F1058FE7710A45FABC0689A7 /* Embed Foundation Extensions */,
CC1B793274FE428D8531E950 /* Embed Foundation Extensions */,
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */,
7D1788E2D4EF42D9BD43136D /* Upload Debug Symbols to Sentry */,
8773BA7C99F44506A4A25842 /* Embed Foundation Extensions */,
0014ED4AAB03BFF76DF3B778 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
8410CAE82E604DD1A187EDA2 /* PBXTargetDependency */,
523C5D7CB8E740A5A0BF4322 /* PBXTargetDependency */,
65AA2578A3174CBBAFE7AF11 /* PBXTargetDependency */,
);
name = Nuvio;
productName = Nuvio;
productReference = 13B07F961A680F5B00A75B9A /* Nuvio.app */;
productType = "com.apple.product-type.application";
};
EFB756D21A05453EA489278C /* LiveActivity */ = {
BB0FA6CBF7FD404994DB1E52 /* LiveActivity */ = {
isa = PBXNativeTarget;
name = LiveActivity;
productName = LiveActivity;
productReference = 49DDF70A2BBD4320BBD94B1B;
productType = "com.apple.product-type.app-extension";
buildConfigurationList = F11E3E24512A427FB847D2F6;
buildConfigurationList = 4D277DDAFD8E470E87E3A678 /* Build configuration list for PBXNativeTarget "LiveActivity" */;
buildPhases = (
784E2472974841CD88391F31 /* Embed Foundation Extensions */,
A2537B59C29048BFB082D5F3 /* Embed Foundation Extensions */,
B06A5B524C284D9FAFC33F3C /* Embed Foundation Extensions */,
547FCD1068DD43D2B6E1582A /* Sources */,
2341847DAC4A41A89AA0DED2 /* Frameworks */,
466E6B41B3D44B44941DC4FF /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = LiveActivity;
productName = LiveActivity;
productReference = 37E2BF6107484CD098F81560 /* LiveActivity.appex */;
productType = "com.apple.product-type.app-extension";
};
/* End PBXNativeTarget section */
@ -420,18 +262,15 @@
attributes = {
LastUpgradeCheck = 1130;
TargetAttributes = {
0EA489F2BF6143F1BA7B8485 = {
DevelopmentTeam = 8QBDZ766S3;
LastSwiftMigration = 1250;
ProvisioningStyle = Automatic;
};
13B07F861A680F5B00A75B9A = {
DevelopmentTeam = 8QBDZ766S3;
LastSwiftMigration = 1250;
DevelopmentTeam = "8QBDZ766S3";
ProvisioningStyle = Automatic;
};
EFB756D21A05453EA489278C = {
BB0FA6CBF7FD404994DB1E52 = {
LastSwiftMigration = 1250;
DevelopmentTeam = "8QBDZ766S3";
ProvisioningStyle = Automatic;
};
};
};
@ -449,8 +288,7 @@
projectRoot = "";
targets = (
13B07F861A680F5B00A75B9A /* Nuvio */,
0EA489F2BF6143F1BA7B8485 /* LiveActivity */,
EFB756D21A05453EA489278C /* LiveActivity */,
BB0FA6CBF7FD404994DB1E52 /* LiveActivity */,
);
};
/* End PBXProject section */
@ -463,29 +301,43 @@
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */,
0FFC28FB1FEA74CCFA112268 /* PrivacyInfo.xcprivacy in Resources */,
24FC08E856045AC6A95D753A /* PrivacyInfo.xcprivacy in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
1E668E0B92C34E73AECDBE1A /* Resources */ = {
466E6B41B3D44B44941DC4FF /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
730F1CE72F24B27100EF7E51 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
B06A5B524C284D9FAFC33F3C /* Embed Foundation Extensions */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
730F1CE72F24B27100EF7E51 /* Assets.xcassets in Resources */,
357A8847EA6B42B792A617D4 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
0014ED4AAB03BFF76DF3B778 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Nuvio/Pods-Nuvio-frameworks.sh",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/React-Core-prebuilt/React.framework/React",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ReactNativeDependencies/ReactNativeDependencies.framework/ReactNativeDependencies",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/React.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactNativeDependencies.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Nuvio/Pods-Nuvio-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@ -503,7 +355,7 @@
shellPath = /bin/sh;
shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n# Source .xcode.env.updates if it exists to allow\n# SKIP_BUNDLING to be unset if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.updates\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.updates\"\nfi\n# Source local changes to allow overrides\n# if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n/bin/sh `\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode.sh'\"` `\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n\n";
};
13C7A3175A582B3D4E9F198E /* [CP] Check Pods Manifest.lock */ = {
08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@ -525,7 +377,21 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
7F1DFB9D902E2DBC35F3FB84 /* [CP] Copy Pods Resources */ = {
7D1788E2D4EF42D9BD43136D /* Upload Debug Symbols to Sentry */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Upload Debug Symbols to Sentry";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh `${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`";
};
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@ -637,7 +503,7 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Nuvio/Pods-Nuvio-resources.sh\"\n";
showEnvVarsInLog = 0;
};
99A79B70155E84EE1FB7F466 /* [Expo] Configure project */ = {
B67FC995125067F91F480913 /* [Expo] Configure project */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
@ -661,42 +527,6 @@
shellPath = /bin/sh;
shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-Nuvio/expo-configure-project.sh\"\n";
};
9B977D89FE30470F8C59964C /* Upload Debug Symbols to Sentry */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Upload Debug Symbols to Sentry";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh `${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`";
};
E043D7E00F2210228303FC0B /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Nuvio/Pods-Nuvio-frameworks.sh",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/React-Core-prebuilt/React.framework/React",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ReactNativeDependencies/ReactNativeDependencies.framework/ReactNativeDependencies",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/React.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactNativeDependencies.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Nuvio/Pods-Nuvio-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@ -705,73 +535,46 @@
buildActionMask = 2147483647;
files = (
F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */,
9FBA88F42E86ECD700892850 /* KSPlayerViewManager.swift in Sources */,
9FBA88F52E86ECD700892850 /* KSPlayerModule.swift in Sources */,
9FBA88F62E86ECD700892850 /* KSPlayerManager.m in Sources */,
9FBA88F72E86ECD700892850 /* KSPlayerView.swift in Sources */,
2AA769395C1242F225F875AF /* ExpoModulesProvider.swift in Sources */,
F4B2944DA5D06650F3B40F2A /* ExpoModulesProvider.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
6E9A0429F8E74948A82DEFF5 /* Sources */ = {
547FCD1068DD43D2B6E1582A /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
730F1CDE2F24B27100EF7E51 /* Color+hex.swift in Sources */,
730F1CDF2F24B27100EF7E51 /* Date+toTimerInterval.swift in Sources */,
730F1CE02F24B27100EF7E51 /* Image+dynamic.swift in Sources */,
730F1CE12F24B27100EF7E51 /* LiveActivityView.swift in Sources */,
730F1CE22F24B27100EF7E51 /* LiveActivityWidget.swift in Sources */,
730F1CE32F24B27100EF7E51 /* LiveActivityWidgetBundle.swift in Sources */,
730F1CE42F24B27100EF7E51 /* View+applyIfPresent.swift in Sources */,
730F1CE52F24B27100EF7E51 /* View+applyWidgetURL.swift in Sources */,
730F1CE62F24B27100EF7E51 /* ViewHelpers.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
784E2472974841CD88391F31 /* Embed Foundation Extensions */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
730F1CDE2F24B27100EF7E51 /* "Color+hex.swift" in Sources */,
730F1CDF2F24B27100EF7E51 /* "Date+toTimerInterval.swift" in Sources */,
730F1CE02F24B27100EF7E51 /* "Image+dynamic.swift" in Sources */,
730F1CE12F24B27100EF7E51 /* LiveActivityView.swift in Sources */,
730F1CE22F24B27100EF7E51 /* LiveActivityWidget.swift in Sources */,
730F1CE32F24B27100EF7E51 /* LiveActivityWidgetBundle.swift in Sources */,
730F1CE42F24B27100EF7E51 /* "View+applyIfPresent.swift" in Sources */,
730F1CE52F24B27100EF7E51 /* "View+applyWidgetURL.swift" in Sources */,
730F1CE62F24B27100EF7E51 /* ViewHelpers.swift in Sources */,
9BE191900481495083B7ECC1 /* Color+hex.swift in Sources */,
3A4B91216E8D486E9000CD6C /* Date+toTimerInterval.swift in Sources */,
B3BB1BBDA8C742DC9A32EACF /* Image+dynamic.swift in Sources */,
D102CD3E3CCC4B83AC0F4DBE /* LiveActivityView.swift in Sources */,
DEB0D346CF964886AD06A268 /* LiveActivityWidget.swift in Sources */,
4F1568831D134295A56C0263 /* LiveActivityWidgetBundle.swift in Sources */,
0512F937B36046F581AF6A55 /* View+applyIfPresent.swift in Sources */,
94F843501BFD40C384CBB500 /* View+applyWidgetURL.swift in Sources */,
7069406A1E324608A86CC6C9 /* ViewHelpers.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
8410CAE82E604DD1A187EDA2 /* PBXTargetDependency */ = {
65AA2578A3174CBBAFE7AF11 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 0EA489F2BF6143F1BA7B8485 /* LiveActivity */;
targetProxy = 55A0DD628D7F4F4F88B4A001 /* PBXContainerItemProxy */;
};
523C5D7CB8E740A5A0BF4322 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = EFB756D21A05453EA489278C /* LiveActivity */;
targetProxy = 7A41A1F529994F0C8801F1A5 /* PBXContainerItemProxy */;
target = BB0FA6CBF7FD404994DB1E52 /* LiveActivity */;
targetProxy = DBC6C07FF91444EEAB5F4C66 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = DAD634845937EAF8D64F20FC /* Pods-Nuvio.debug.xcconfig */;
baseConfigurationReference = 7EC866223BB0FA7280D468BF /* Pods-Nuvio.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Nuvio/Nuvio.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 8QBDZ766S3;
DEVELOPMENT_TEAM = "8QBDZ766S3";
ENABLE_BITCODE = NO;
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
@ -791,27 +594,26 @@
);
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.hub;
PRODUCT_NAME = "Nuvio";
SUPPORTS_MACCATALYST = YES;
PRODUCT_NAME = Nuvio;
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
};
name = Debug;
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 0DFF64A670930CED5EA4DF3A /* Pods-Nuvio.release.xcconfig */;
baseConfigurationReference = 44393AD0ABDBAED5B40D2E49 /* Pods-Nuvio.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Nuvio/NuvioRelease.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CODE_SIGN_ENTITLEMENTS = Nuvio/Nuvio.entitlements;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 8QBDZ766S3;
DEVELOPMENT_TEAM = "8QBDZ766S3";
INFOPLIST_FILE = Nuvio/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD_RUNPATH_SEARCH_PATHS = (
@ -826,38 +628,15 @@
);
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.hub;
PRODUCT_NAME = "Nuvio";
SUPPORTS_MACCATALYST = YES;
PRODUCT_NAME = Nuvio;
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
3DCEA1FBF99E46F58A7150CC /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CODE_SIGN_ENTITLEMENTS = LiveActivity/LiveActivity.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 34;
DEVELOPMENT_TEAM = 8QBDZ766S3;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = LiveActivity/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = LiveActivity;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
MARKETING_VERSION = 1.3.6;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.hub.LiveActivity;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
name = Release;
};
83CBBA201A601CBA00E9B192 /* Debug */ = {
isa = XCBuildConfiguration;
@ -977,70 +756,54 @@
};
name = Release;
};
E4108F64486C48E192EAA45D /* Release */ = {
935F73C6BA4D42D1A1F36953 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CODE_SIGN_ENTITLEMENTS = LiveActivity/LiveActivity.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 34;
DEVELOPMENT_TEAM = 8QBDZ766S3;
CURRENT_PROJECT_VERSION = 37;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = LiveActivity/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = LiveActivity;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
MARKETING_VERSION = 1.3.6;
MARKETING_VERSION = 1.4.1;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.hub.LiveActivity;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
DEVELOPMENT_TEAM = "8QBDZ766S3";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
};
name = Debug;
};
DE24FDCA00464F85805C3A58 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CODE_SIGN_ENTITLEMENTS = LiveActivity/LiveActivity.entitlements;
CURRENT_PROJECT_VERSION = 37;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = LiveActivity/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = LiveActivity;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
MARKETING_VERSION = 1.4.1;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.hub.LiveActivity;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
DEVELOPMENT_TEAM = "8QBDZ766S3";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
};
name = Release;
};
B062D46778AF40DE92953986 /* Debug */ = {
name = Debug;
isa = XCBuildConfiguration;
buildSettings = {
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
INFOPLIST_FILE = LiveActivity/Info.plist;
CURRENT_PROJECT_VERSION = "37";
IPHONEOS_DEPLOYMENT_TARGET = "16.2";
PRODUCT_BUNDLE_IDENTIFIER = "com.nuvio.hub.LiveActivity";
GENERATE_INFOPLIST_FILE = "YES";
INFOPLIST_KEY_CFBundleDisplayName = LiveActivity;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
MARKETING_VERSION = "1.4.1";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
CODE_SIGN_ENTITLEMENTS = "LiveActivity/LiveActivity.entitlements";
APPLICATION_EXTENSION_API_ONLY = "YES";
};
};
67617475B4F443CBBCE1A6FD /* Release */ = {
name = Release;
isa = XCBuildConfiguration;
buildSettings = {
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
INFOPLIST_FILE = LiveActivity/Info.plist;
CURRENT_PROJECT_VERSION = "37";
IPHONEOS_DEPLOYMENT_TARGET = "16.2";
PRODUCT_BUNDLE_IDENTIFIER = "com.nuvio.hub.LiveActivity";
GENERATE_INFOPLIST_FILE = "YES";
INFOPLIST_KEY_CFBundleDisplayName = LiveActivity;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
MARKETING_VERSION = "1.4.1";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
CODE_SIGN_ENTITLEMENTS = "LiveActivity/LiveActivity.entitlements";
APPLICATION_EXTENSION_API_ONLY = "YES";
};
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@ -1053,6 +816,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
4D277DDAFD8E470E87E3A678 /* Build configuration list for PBXNativeTarget "LiveActivity" */ = {
isa = XCConfigurationList;
buildConfigurations = (
935F73C6BA4D42D1A1F36953 /* Debug */,
DE24FDCA00464F85805C3A58 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "Nuvio" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@ -1062,24 +834,6 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C95083D445BA485B82D2FFBC /* Build configuration list for PBXNativeTarget "LiveActivity" */ = {
isa = XCConfigurationList;
buildConfigurations = (
3DCEA1FBF99E46F58A7150CC /* Debug */,
E4108F64486C48E192EAA45D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
F11E3E24512A427FB847D2F6 /* Build configuration list for PBXNativeTarget "LiveActivity" */ = {
isa = XCConfigurationList;
buildConfigurations = (
B062D46778AF40DE92953986 /* Debug */,
67617475B4F443CBBCE1A6FD /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;

View file

@ -40,6 +40,15 @@
</array>
<key>CFBundleVersion</key>
<string>37</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>vlc</string>
<string>vlc-x-callback</string>
<string>infuse</string>
<string>outplayer</string>
<string>open-vidhub</string>
<string>livecontainer</string>
</array>
<key>LSMinimumSystemVersion</key>
<string>12.0</string>
<key>LSRequiresIPhoneOS</key>

View file

@ -4,14 +4,14 @@ PODS:
- ExpoModulesCore
- EXApplication (7.0.8):
- ExpoModulesCore
- EXConstants (18.0.12):
- EXConstants (18.0.13):
- ExpoModulesCore
- EXJSONUtils (0.15.0)
- EXManifests (1.0.10):
- ExpoModulesCore
- EXNotifications (0.32.15):
- EXNotifications (0.32.16):
- ExpoModulesCore
- Expo (54.0.29):
- Expo (54.0.33):
- ExpoModulesCore
- hermes-engine
- RCTRequired
@ -207,7 +207,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- ExpoAsset (12.0.11):
- ExpoAsset (12.0.12):
- ExpoModulesCore
- ExpoBlur (15.0.8):
- ExpoModulesCore
@ -223,9 +223,9 @@ PODS:
- ExpoModulesCore
- ExpoFileSystem (19.0.21):
- ExpoModulesCore
- ExpoFont (14.0.10):
- ExpoFont (14.0.11):
- ExpoModulesCore
- ExpoGlassEffect (0.1.8):
- ExpoGlassEffect (0.1.9):
- ExpoModulesCore
- ExpoHaptics (15.0.8):
- ExpoModulesCore
@ -233,7 +233,7 @@ PODS:
- ExpoModulesCore
- ExpoLinearGradient (15.0.8):
- ExpoModulesCore
- ExpoLinking (8.0.10):
- ExpoLinking (8.0.11):
- ExpoModulesCore
- ExpoLiveActivity (0.4.2):
- ExpoModulesCore
@ -294,7 +294,7 @@ PODS:
- ExpoWebBrowser (15.0.10):
- ExpoModulesCore
- EXStructuredHeaders (5.0.0)
- EXUpdates (29.0.15):
- EXUpdates (29.0.16):
- EASClient
- EXManifests
- ExpoModulesCore
@ -333,8 +333,9 @@ PODS:
- hermes-engine (0.81.4):
- hermes-engine/Pre-built (= 0.81.4)
- hermes-engine/Pre-built (0.81.4)
- ImageColors (2.5.1):
- ImageColors (2.6.0):
- ExpoModulesCore
- SwiftDraw (~> 0.27)
- KSPlayer (1.1.0):
- KSPlayer/Audio (= 1.1.0)
- KSPlayer/AVPlayer (= 1.1.0)
@ -382,10 +383,10 @@ PODS:
- libwebp/sharpyuv (1.5.0)
- libwebp/webp (1.5.0):
- libwebp/sharpyuv
- lottie-ios (4.5.0)
- lottie-react-native (7.3.4):
- lottie-ios (4.6.0)
- lottie-react-native (7.3.6):
- hermes-engine
- lottie-ios (= 4.5.0)
- lottie-ios (= 4.6.0)
- RCTRequired
- RCTTypeSafety
- React-Core
@ -406,12 +407,12 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- MMKV (2.2.4):
- MMKVCore (~> 2.2.4)
- MMKVCore (2.2.4)
- NitroMmkv (4.1.0):
- MMKV (2.3.0):
- MMKVCore (~> 2.3.0)
- MMKVCore (2.3.0)
- NitroMmkv (4.2.0):
- hermes-engine
- MMKVCore (= 2.2.4)
- MMKVCore (= 2.3.0)
- NitroModules
- RCTRequired
- RCTTypeSafety
@ -434,7 +435,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- NitroModules (0.31.10):
- NitroModules (0.35.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -1738,7 +1739,7 @@ PODS:
- React-RCTFBReactNativeSpec
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- react-native-background-downloader (4.4.5):
- react-native-background-downloader (4.5.3):
- hermes-engine
- MMKV
- RCTRequired
@ -1858,33 +1859,7 @@ PODS:
- google-cast-sdk
- PromisesObjC
- React
- react-native-netinfo (11.4.1):
- React-Core
- react-native-safe-area-context (5.6.2):
- hermes-engine
- RCTRequired
- RCTTypeSafety
- React-Core
- React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-jsi
- react-native-safe-area-context/common (= 5.6.2)
- react-native-safe-area-context/fabric (= 5.6.2)
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- react-native-safe-area-context/common (5.6.2):
- react-native-netinfo (12.0.1):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -1906,7 +1881,53 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- react-native-safe-area-context/fabric (5.6.2):
- react-native-safe-area-context (5.7.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
- React-Core
- React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-jsi
- react-native-safe-area-context/common (= 5.7.0)
- react-native-safe-area-context/fabric (= 5.7.0)
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- react-native-safe-area-context/common (5.7.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
- React-Core
- React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-jsi
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- react-native-safe-area-context/fabric (5.7.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -1929,7 +1950,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- react-native-skia (2.4.14):
- react-native-skia (2.5.1):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -1953,7 +1974,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- react-native-slider (5.1.1):
- react-native-slider (5.1.2):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -1965,7 +1986,7 @@ PODS:
- React-graphics
- React-ImageManager
- React-jsi
- react-native-slider/common (= 5.1.1)
- react-native-slider/common (= 5.1.2)
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
@ -1976,7 +1997,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- react-native-slider/common (5.1.1):
- react-native-slider/common (5.1.2):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2466,7 +2487,7 @@ PODS:
- SDWebImageSVGCoder (~> 1.7.0)
- SDWebImageWebPCoder (~> 0.14)
- Yoga
- RNGestureHandler (2.29.1):
- RNGestureHandler (2.30.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2488,7 +2509,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- RNReanimated (4.2.0):
- RNReanimated (4.2.2):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2510,10 +2531,10 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- RNReanimated/reanimated (= 4.2.0)
- RNReanimated/reanimated (= 4.2.2)
- RNWorklets
- Yoga
- RNReanimated/reanimated (4.2.0):
- RNReanimated/reanimated (4.2.2):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2535,10 +2556,10 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- RNReanimated/reanimated/apple (= 4.2.0)
- RNReanimated/reanimated/apple (= 4.2.2)
- RNWorklets
- Yoga
- RNReanimated/reanimated/apple (4.2.0):
- RNReanimated/reanimated/apple (4.2.2):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2562,7 +2583,7 @@ PODS:
- ReactNativeDependencies
- RNWorklets
- Yoga
- RNScreens (4.18.0):
- RNScreens (4.24.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2584,9 +2605,9 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- RNScreens/common (= 4.18.0)
- RNScreens/common (= 4.24.0)
- Yoga
- RNScreens/common (4.18.0):
- RNScreens/common (4.24.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2609,7 +2630,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- RNSentry (7.7.0):
- RNSentry (8.4.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2631,9 +2652,9 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Sentry/HybridSDK (= 8.57.3)
- Sentry (= 9.7.0)
- Yoga
- RNSVG (15.15.1):
- RNSVG (15.15.3):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2654,9 +2675,9 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- RNSVG/common (= 15.15.1)
- RNSVG/common (= 15.15.3)
- Yoga
- RNSVG/common (15.15.1):
- RNSVG/common (15.15.3):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2700,7 +2721,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- RNWorklets (0.7.1):
- RNWorklets (0.7.4):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2722,9 +2743,9 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- RNWorklets/worklets (= 0.7.1)
- RNWorklets/worklets (= 0.7.4)
- Yoga
- RNWorklets/worklets (0.7.1):
- RNWorklets/worklets (0.7.4):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2746,9 +2767,9 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- RNWorklets/worklets/apple (= 0.7.1)
- RNWorklets/worklets/apple (= 0.7.4)
- Yoga
- RNWorklets/worklets/apple (0.7.1):
- RNWorklets/worklets/apple (0.7.4):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2771,9 +2792,9 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- SDWebImage (5.21.5):
- SDWebImage/Core (= 5.21.5)
- SDWebImage/Core (5.21.5)
- SDWebImage (5.21.7):
- SDWebImage/Core (= 5.21.7)
- SDWebImage/Core (5.21.7)
- SDWebImageAVIFCoder (0.11.1):
- libavif/core (>= 0.11.0)
- SDWebImage (~> 5.10)
@ -2782,7 +2803,12 @@ PODS:
- SDWebImageWebPCoder (0.15.0):
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.17)
- Sentry/HybridSDK (8.57.3)
- Sentry (9.7.0):
- Sentry/Core (= 9.7.0)
- Sentry/Core (9.7.0)
- SwiftDraw (0.27.0):
- SwiftDrawDOM (~> 0.27.0)
- SwiftDrawDOM (0.27.0)
- SwiftUIIntrospect (1.3.0)
- Yoga (0.0.0)
@ -2936,6 +2962,8 @@ SPEC REPOS:
- SDWebImageSVGCoder
- SDWebImageWebPCoder
- Sentry
- SwiftDraw
- SwiftDrawDOM
- SwiftUIIntrospect
EXTERNAL SOURCES:
@ -3217,16 +3245,16 @@ SPEC CHECKSUMS:
DisplayCriteria: bb0a90faf14b30848bc50ac0516340ce50164187
EASClient: 40dd9e740684782610c49becab2643782ea1a20c
EXApplication: 1e98d4b1dccdf30627f92917f4b2c5a53c330e5f
EXConstants: 805f35b1b295c542ca6acce836f21a1f9ee104d5
EXConstants: fce59a631a06c4151602843667f7cfe35f81e271
EXJSONUtils: 1d3e4590438c3ee593684186007028a14b3686cd
EXManifests: a8d97683e5c7a3b026ffbd58559c64dc655b747b
EXNotifications: 983f04ad4ad879b181179e326bf220541e478386
Expo: 8fa2204bf8483fe546b4ec87c90d3ca189afc8db
EXNotifications: 9eec98712cc814ceff916d876cb53859003b0597
Expo: aadbcc8c6c14ff3105a9154f1683cb1424ff0d2a
expo-dev-client: 425ee077d6754a98cfe3a2e2410d29b440b24c9d
expo-dev-launcher: a4f4cdef064ab1fb8621e5b8c7c457cd6e9568c3
expo-dev-menu: 05b18812110c175814c6af0d09dd658abcc5e00d
expo-dev-menu-interface: 600df12ea01efecdd822daaf13cc0ac091775533
ExpoAsset: 23a958e97d3d340919fe6774db35d563241e6c03
ExpoAsset: f867e55ceb428aab99e1e8c082b5aee7c159ea18
ExpoBlur: b90747a3f22a8b6ceffd9cb0dc41a4184efdc656
ExpoBrightness: 46c980463e8a54b9ce77f923c4bff0bb0c9526e0
ExpoClipboard: b36b287d8356887844bb08ed5c84b5979bb4dd1e
@ -3234,12 +3262,12 @@ SPEC CHECKSUMS:
ExpoDevice: 6327c3c200816795708885adf540d26ecab83d1a
ExpoDocumentPicker: 7cd9e71a0f66fb19eb0a586d6f26eee1284692e0
ExpoFileSystem: 858a44267a3e6e9057e0888ad7c7cfbf55d52063
ExpoFont: 35ac6191ed86bbf56b3ebd2d9154eda9fad5b509
ExpoGlassEffect: 8ce45eca31f12e949e23a4ee13e2bfb59e9b0785
ExpoFont: f543ce20a228dd702813668b1a07b46f51878d47
ExpoGlassEffect: 93f0665d19063cb3b46bbd0a2ebea4a36b6a4e37
ExpoHaptics: d3a6375d8dcc3a1083d003bc2298ff654fafb536
ExpoKeepAwake: 55f75eca6499bb9e4231ebad6f3e9cb8f99c0296
ExpoLinearGradient: 809102bdb979f590083af49f7fa4805cd931bd58
ExpoLinking: f4c4a351523da72a6bfa7e1f4ca92aee1043a3ca
ExpoLinking: 8f0aaf69aa56f832913030503b6263dc6f647f37
ExpoLiveActivity: d0dd0e8e1460b6b26555b611c4826cdb1036eea2
ExpoLocalization: d9168d5300a5b03e5e78b986124d11fb6ec3ebbd
ExpoModulesCore: f3da4f1ab5a8375d0beafab763739dbee8446583
@ -3249,24 +3277,24 @@ SPEC CHECKSUMS:
ExpoSystemUI: 2ad325f361a2fcd96a464e8574e19935c461c9cc
ExpoWebBrowser: 17b064c621789e41d4816c95c93f429b84971f52
EXStructuredHeaders: c951e77f2d936f88637421e9588c976da5827368
EXUpdates: f20abbc8a9f4e150656fe88126d52f52d4e7793f
EXUpdates: f86d4af4362c2c83a7bf8531d777e9fba680e2d9
EXUpdatesInterface: 5adf50cb41e079c861da6d9b4b954c3db9a50734
FBLazyVector: 9e0cd874afd81d9a4d36679daca991b58b260d42
FFmpegKit: 3885085fbbc320745838ee4c8a1f9c5e5953dab2
google-cast-sdk: 32f65af50d164e3c475e79ad123db3cc26fbcd37
hermes-engine: 35c763d57c9832d0eef764316ca1c4d043581394
ImageColors: e12eb73e29bc1feaa3c228db8c174a1b25acb59d
ImageColors: 3019b37809023d67795162135628e367d70e1766
KSPlayer: f163ac6195f240b6fa5b8225aeb39ec811a70c62
Libass: e88af2324e1217e3a4c8bdc675f6f23a9dfc7677
libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7
libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
lottie-ios: a881093fab623c467d3bce374367755c272bdd59
lottie-react-native: cbe3d931a7c24f7891a8e8032c2bb9b2373c4b9c
MMKV: 1a8e7dbce7f9cad02c52e1b1091d07bd843aefaf
MMKVCore: f2dd4c9befea04277a55e84e7812f930537993df
NitroMmkv: 4af10c70043b4c3cded3f16547627c7d9d8e3b8b
NitroModules: a71a5ab2911caf79e45170e6e12475b5260a12d0
lottie-ios: 8f959969761e9c45d70353667d00af0e5b9cadb3
lottie-react-native: 6a080b2f109ef611c75c503a33ebb8ea75db0c91
MMKV: c953dbaac0da392c24b005e763c03ce2638b4ed7
MMKVCore: d078dce7d6586a888b2c2ef5343b6242678e3ee8
NitroMmkv: 38dfb2983c83e9bbbb64423ff48fbeaaa097cc38
NitroModules: ff0d24be334de628166b9ac63661c998c732de7d
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
RCTDeprecation: 7487d6dda857ccd4cb3dd6ecfccdc3170e85dcbc
RCTRequired: 54128b7df8be566881d48c7234724a78cb9b6157
@ -3302,16 +3330,16 @@ SPEC CHECKSUMS:
React-logger: 7b234de35acb469ce76d6bbb0457f664d6f32f62
React-Mapbuffer: fbe1da882a187e5898bdf125e1cc6e603d27ecae
React-microtasksnativemodule: 76905804171d8ccbe69329fc84c57eb7934add7f
react-native-background-downloader: 384c954ba4510de725697f7df4fd75f7c25579a2
react-native-background-downloader: 19eb7c5a834bac72f1273ab25325c71c44953c49
react-native-blur: 1b00ef07fe0efdc0c40b37139a5268ccad73c72d
react-native-bottom-tabs: bcb70e4fae95fc9da0da875f7414acda26dfc551
react-native-device-brightness: 1a997350d060c3df9f303b1df84a4f7c5cbeb924
react-native-get-random-values: a603782b2b222a34533c66371614790282dba3f1
react-native-google-cast: 7be68a5d0b7eeb95a5924c3ecef8d319ef6c0a44
react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187
react-native-safe-area-context: 37e680fc4cace3c0030ee46e8987d24f5d3bdab2
react-native-skia: 268f183f849742e9da216743ee234bd7ad81c69b
react-native-slider: f954578344106f0a732a4358ce3a3e11015eb6e1
react-native-netinfo: 7e0b1936e928fa8ddb60da9e572a33767db7805f
react-native-safe-area-context: ae7587b95fb580d1800c5b0b2a7bd48c2868e67a
react-native-skia: 6a51463f8391c82f675f490c8078135925285a8f
react-native-slider: 8b9a218d1a3e526146a170cb6133be9cda23e70e
react-native-video: bca076cfff2a3e749fc63b3ac88118e1d8ee2689
React-NativeModulesApple: a9464983ccc0f66f45e93558671f60fc7536e438
React-oscompat: 73db7dbc80edef36a9d6ed3c6c4e1724ead4236d
@ -3346,18 +3374,20 @@ SPEC CHECKSUMS:
ReactNativeDependencies: ed6d1e64802b150399f04f1d5728ec16b437251e
RNCPicker: c8a3584b74133464ee926224463fcc54dfdaebca
RNFastImage: 2d36f4cfed9b2342f94f8591c8be69dd047ac67c
RNGestureHandler: 723f29dac55e25f109d263ed65cecc4b9c4bd46a
RNReanimated: e1c71e6e693a66b203ae98773347b625d3cc85ee
RNScreens: 61c18865ab074f4d995ac8d7cf5060522a649d05
RNSentry: 1d7b9fdae7a01ad8f9053335b5d44e75c39a955e
RNSVG: cf9ae78f2edf2988242c71a6392d15ff7dd62522
RNGestureHandler: e0d0bce5599f6120b7adf90c38d2805e2935795f
RNReanimated: f4644326ad2bc0f5c0e52f15db316d4f5ee77a60
RNScreens: 6cb648bdad8fe9bee9259fe144df95b6d1d5b707
RNSentry: 4e9fc32104771fbb20c5f567eb9299fd92ea48b8
RNSVG: 7612f5bc575eab5da3364d44abe9f0d5db2cde03
RNVectorIcons: 4351544f100d4f12cac156a7c13399e60bab3e26
RNWorklets: 9eb6d567fa43984e96b6924a6df504b8a15980cd
SDWebImage: e9c98383c7572d713c1a0d7dd2783b10599b9838
RNWorklets: eb8d899de8701d58c6a634b8468ce2a6db3113b2
SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf
SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57
SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c
SDWebImageWebPCoder: 0e06e365080397465cc73a7a9b472d8a3bd0f377
Sentry: c643eb180df401dd8c734c5036ddd9dd9218daa6
Sentry: 0fe17abdc98f83a3799c02564382d8b2afabadef
SwiftDraw: c3fb77c08081f1255f33bcc0fd3c2e00fbc9bd46
SwiftDrawDOM: 8e7d9812af1ef6e06230e72769e97838e6c7c765
SwiftUIIntrospect: fee9aa07293ee280373a591e1824e8ddc869ba5d
Yoga: 051f086b5ccf465ff2ed38a2cf5a558ae01aaaa1

View file

@ -864,8 +864,19 @@ public class ReactExoplayerView extends FrameLayout implements
drmSessionManager,
runningSource.getCropStartMs(),
runningSource.getCropEndMs());
MediaSource mediaSourceWithAds = initializeAds(videoSource, runningSource);
MediaSource mediaSource = Objects.requireNonNullElse(mediaSourceWithAds, videoSource);
MediaSource mergedSource = videoSource;
if (runningSource.getAudioUri() != null) {
MediaSource audioSource = buildMediaSource(
runningSource.getAudioUri(),
null,
null,
-1,
-1
);
mergedSource = new MergingMediaSource(true, videoSource, audioSource);
}
MediaSource mediaSourceWithAds = initializeAds(mergedSource, runningSource);
MediaSource mediaSource = Objects.requireNonNullElse(mediaSourceWithAds, mergedSource);
// wait for player to be set
while (player == null) {
@ -1100,8 +1111,17 @@ public class ReactExoplayerView extends FrameLayout implements
}
} else if ("file".equals(uri.getScheme()) ||
!useCache) {
DataSource.Factory progressiveDataSourceFactory = mediaDataSourceFactory;
String host = uri.getHost();
if (host != null && host.contains("googlevideo.com")) {
progressiveDataSourceFactory = DataSourceUtil.buildYoutubeChunkedDataSourceFactory(
themedReactContext,
bandwidthMeter,
source.getHeaders()
);
}
mediaSourceFactory = new ProgressiveMediaSource.Factory(
mediaDataSourceFactory
progressiveDataSourceFactory
);
} else {
mediaSourceFactory = new ProgressiveMediaSource.Factory(

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,269 @@
const DEFAULT_USER_AGENT =
'Mozilla/5.0 (Linux; Android 12; Android TV) AppleWebKit/537.36 ' +
'(KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36';
const DEFAULT_HEADERS = {
'accept-language': 'en-US,en;q=0.9',
'user-agent': DEFAULT_USER_AGENT,
};
const CLIENTS = [
{
key: 'android_vr',
id: '28',
version: '1.62.27',
userAgent:
'com.google.android.apps.youtube.vr.oculus/1.62.27 ' +
'(Linux; U; Android 12; en_US; Quest 3; Build/SQ3A.220605.009.A1) gzip',
context: {
clientName: 'ANDROID_VR',
clientVersion: '1.62.27',
deviceMake: 'Oculus',
deviceModel: 'Quest 3',
osName: 'Android',
osVersion: '12',
platform: 'MOBILE',
androidSdkVersion: 32,
hl: 'en',
gl: 'US',
},
},
{
key: 'android',
id: '3',
version: '20.10.38',
userAgent:
'com.google.android.youtube/20.10.38 (Linux; U; Android 14; en_US) gzip',
context: {
clientName: 'ANDROID',
clientVersion: '20.10.38',
osName: 'Android',
osVersion: '14',
platform: 'MOBILE',
androidSdkVersion: 34,
hl: 'en',
gl: 'US',
},
},
{
key: 'ios',
id: '5',
version: '20.10.1',
userAgent:
'com.google.ios.youtube/20.10.1 (iPhone16,2; U; CPU iOS 17_4 like Mac OS X)',
context: {
clientName: 'IOS',
clientVersion: '20.10.1',
deviceModel: 'iPhone16,2',
osName: 'iPhone',
osVersion: '17.4.0.21E219',
platform: 'MOBILE',
hl: 'en',
gl: 'US',
},
},
];
function parseVideoId(input) {
if (!input) return null;
const trimmed = input.trim();
if (/^[A-Za-z0-9_-]{11}$/.test(trimmed)) return trimmed;
try {
const url = new URL(trimmed.startsWith('http') ? trimmed : `https://${trimmed}`);
if (url.hostname.endsWith('youtu.be')) {
const id = url.pathname.slice(1).split('/')[0];
if (/^[A-Za-z0-9_-]{11}$/.test(id)) return id;
}
const v = url.searchParams.get('v');
if (v && /^[A-Za-z0-9_-]{11}$/.test(v)) return v;
} catch {}
return null;
}
function getMimeBase(mimeType = '') {
return mimeType.split(';')[0].trim();
}
function getExt(mimeType = '') {
const base = getMimeBase(mimeType);
if (base === 'video/mp4' || base === 'audio/mp4') return 'mp4';
if (base.includes('webm')) return 'webm';
if (base.includes('m4a')) return 'm4a';
return 'other';
}
function parseQualityLabel(label = '') {
const match = label.match(/(\d{2,4})p/);
return match ? Number.parseInt(match[1], 10) : 0;
}
function videoScore(height, fps, bitrate) {
return height * 1_000_000_000 + fps * 1_000_000 + bitrate;
}
function audioScore(bitrate, sampleRate) {
return bitrate * 1_000_000 + sampleRate;
}
function sortCandidates(items) {
return [...items].sort((a, b) => b.score - a.score);
}
function isIosSafeVideo(candidate) {
const mimeBase = getMimeBase(candidate.mimeType);
return mimeBase === 'video/mp4';
}
function isIosSafeAudio(candidate) {
const mimeBase = getMimeBase(candidate.mimeType);
return mimeBase === 'audio/mp4' || candidate.ext === 'm4a';
}
async function fetchWatchConfig(videoId) {
const response = await fetch(`https://www.youtube.com/watch?v=${videoId}&hl=en`, {
headers: DEFAULT_HEADERS,
});
if (!response.ok) {
throw new Error(`watch page failed: ${response.status}`);
}
const html = await response.text();
return {
apiKey: html.match(/"INNERTUBE_API_KEY":"([^"]+)"/)?.[1] ?? null,
visitorData: html.match(/"VISITOR_DATA":"([^"]+)"/)?.[1] ?? null,
};
}
async function fetchPlayerResponse(videoId, apiKey, visitorData, client) {
const endpoint = apiKey
? `https://www.youtube.com/youtubei/v1/player?key=${encodeURIComponent(apiKey)}&prettyPrint=false`
: `https://www.youtube.com/youtubei/v1/player?prettyPrint=false`;
const headers = {
...DEFAULT_HEADERS,
'content-type': 'application/json',
origin: 'https://www.youtube.com',
'x-youtube-client-name': client.id,
'x-youtube-client-version': client.version,
'user-agent': client.userAgent,
...(visitorData ? { 'x-goog-visitor-id': visitorData } : {}),
};
const payload = {
videoId,
contentCheckOk: true,
racyCheckOk: true,
context: { client: client.context },
playbackContext: {
contentPlaybackContext: { html5Preference: 'HTML5_PREF_WANTS' },
},
};
const response = await fetch(endpoint, {
method: 'POST',
headers,
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(`player API ${client.key} failed: ${response.status}`);
}
return response.json();
}
async function main() {
const input = process.argv[2];
const videoId = parseVideoId(input);
if (!videoId) {
console.error('Usage: node scripts/inspect-youtube-formats.mjs <youtube-id-or-url>');
process.exit(1);
}
const { apiKey, visitorData } = await fetchWatchConfig(videoId);
if (!apiKey) {
throw new Error('Could not extract INNERTUBE_API_KEY');
}
const adaptiveVideo = [];
const adaptiveAudio = [];
for (const client of CLIENTS) {
const data = await fetchPlayerResponse(videoId, apiKey, visitorData, client);
const formats = data?.streamingData?.adaptiveFormats ?? [];
for (const f of formats) {
if (!f.url) continue;
const mimeBase = getMimeBase(f.mimeType);
if (mimeBase.startsWith('video/')) {
const height = f.height ?? parseQualityLabel(f.qualityLabel);
const fps = f.fps ?? 0;
const bitrate = f.bitrate ?? f.averageBitrate ?? 0;
adaptiveVideo.push({
client: client.key,
mimeType: f.mimeType ?? '',
ext: getExt(f.mimeType),
height,
fps,
bitrate,
score: videoScore(height, fps, bitrate),
url: f.url,
});
} else if (mimeBase.startsWith('audio/')) {
const bitrate = f.bitrate ?? f.averageBitrate ?? 0;
const sampleRate = Number.parseFloat(f.audioSampleRate ?? '0') || 0;
adaptiveAudio.push({
client: client.key,
mimeType: f.mimeType ?? '',
ext: getExt(f.mimeType),
bitrate,
audioSampleRate: f.audioSampleRate ?? '',
score: audioScore(bitrate, sampleRate),
url: f.url,
});
}
}
}
const sortedVideo = sortCandidates(adaptiveVideo);
const sortedAudio = sortCandidates(adaptiveAudio);
const iosSafeVideo = sortedVideo.filter(isIosSafeVideo);
const iosSafeAudio = sortedAudio.filter(isIosSafeAudio);
console.log(`Video ID: ${videoId}`);
console.log('');
console.log('Top adaptive video candidates:');
for (const item of sortedVideo.slice(0, 8)) {
console.log(
`- client=${item.client} height=${item.height} fps=${item.fps} bitrate=${item.bitrate} ext=${item.ext} mime=${item.mimeType}`
);
}
console.log('');
console.log('Top adaptive audio candidates:');
for (const item of sortedAudio.slice(0, 12)) {
console.log(
`- client=${item.client} bitrate=${item.bitrate} sampleRate=${item.audioSampleRate} ext=${item.ext} mime=${item.mimeType}`
);
}
console.log('');
console.log('Top iOS-safe video candidates:');
for (const item of iosSafeVideo.slice(0, 8)) {
console.log(
`- client=${item.client} height=${item.height} fps=${item.fps} bitrate=${item.bitrate} ext=${item.ext} mime=${item.mimeType}`
);
}
console.log('');
console.log('Top iOS-safe audio candidates:');
for (const item of iosSafeAudio.slice(0, 8)) {
console.log(
`- client=${item.client} bitrate=${item.bitrate} sampleRate=${item.audioSampleRate} ext=${item.ext} mime=${item.mimeType}`
);
}
}
main().catch((error) => {
console.error(error instanceof Error ? error.stack ?? error.message : String(error));
process.exit(1);
});

View file

@ -40,7 +40,7 @@ import { logger } from '../../utils/logger';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useSettings } from '../../hooks/useSettings';
import { useTrailer } from '../../contexts/TrailerContext';
import TrailerService from '../../services/trailerService';
import TrailerService, { TrailerPlaybackSource } from '../../services/trailerService';
import TrailerPlayer from '../video/TrailerPlayer';
import { useLibrary } from '../../hooks/useLibrary';
import { useToast } from '../../contexts/ToastContext';
@ -202,7 +202,7 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
const [initialLoadComplete, setInitialLoadComplete] = useState(false);
// Trailer state
const [trailerUrl, setTrailerUrl] = useState<string | null>(null);
const [trailerSource, setTrailerSource] = useState<TrailerPlaybackSource | null>(null);
const [trailerLoading, setTrailerLoading] = useState(false);
const [trailerError, setTrailerError] = useState(false);
const [trailerReady, setTrailerReady] = useState(false);
@ -392,14 +392,14 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
setTrailerShouldBePaused(false);
// If trailer was ready and loaded, restore the video opacity
if (trailerReady && trailerUrl) {
if (trailerReady && trailerSource?.videoUrl) {
logger.info('[AppleTVHero] Screen in focus and in view - restoring trailer');
thumbnailOpacity.value = withTiming(0, { duration: 800 });
trailerOpacity.value = withTiming(1, { duration: 800 });
setTrailerPlaying(true);
}
}
}, [isFocused, isOutOfView, setTrailerPlaying, trailerOpacity, thumbnailOpacity, trailerReady, trailerUrl]);
}, [isFocused, isOutOfView, setTrailerPlaying, trailerOpacity, thumbnailOpacity, trailerReady, trailerSource]);
// Listen to navigation events to stop trailer when navigating to other screens
useEffect(() => {
@ -425,7 +425,7 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
const fetchTrailer = async () => {
if (!currentItem || !showTrailersEnabled.current) {
setTrailerUrl(null);
setTrailerSource(null);
return;
}
@ -448,7 +448,7 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
if (!tmdbId) {
logger.info('[AppleTVHero] No TMDB ID for:', currentItem.name, '- skipping trailer');
setTrailerUrl(null);
setTrailerSource(null);
setTrailerLoading(false);
return;
}
@ -467,7 +467,7 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
if (!videosRes.ok) {
logger.warn('[AppleTVHero] TMDB videos fetch failed:', videosRes.status);
setTrailerUrl(null);
setTrailerSource(null);
setTrailerLoading(false);
return;
}
@ -485,31 +485,31 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
if (!pick) {
logger.info('[AppleTVHero] No YouTube video found for:', currentItem.name);
setTrailerUrl(null);
setTrailerSource(null);
setTrailerLoading(false);
return;
}
logger.info('[AppleTVHero] Extracting stream for videoId:', pick.key, currentItem.name);
const url = await TrailerService.getTrailerFromVideoId(
const source = await TrailerService.getTrailerPlaybackSourceFromVideoId(
pick.key,
currentItem.name
);
if (!alive) return;
if (url) {
setTrailerUrl(url);
if (source) {
setTrailerSource(source);
} else {
logger.info('[AppleTVHero] No stream extracted for:', currentItem.name);
setTrailerUrl(null);
setTrailerSource(null);
}
} catch (error) {
if (!alive) return;
logger.error('[AppleTVHero] Error fetching trailer:', error);
setTrailerError(true);
setTrailerUrl(null);
setTrailerSource(null);
} finally {
if (alive) {
setTrailerLoading(false);
@ -1094,11 +1094,12 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
)}
{/* Hidden preload trailer player */}
{settings?.showTrailers && trailerUrl && !trailerLoading && !trailerError && !trailerPreloaded && (
{settings?.showTrailers && trailerSource?.videoUrl && !trailerLoading && !trailerError && !trailerPreloaded && (
<View style={[StyleSheet.absoluteFillObject, { opacity: 0, pointerEvents: 'none' }]}>
<TrailerPlayer
key={`preload-${trailerUrl}`}
trailerUrl={trailerUrl}
key={`preload-${trailerSource.videoUrl}-${trailerSource.audioUrl ?? 'no-audio'}`}
trailerUrl={trailerSource.videoUrl}
audioUrl={trailerSource.audioUrl}
autoPlay={false}
muted={true}
style={StyleSheet.absoluteFillObject}
@ -1112,13 +1113,14 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
)}
{/* Visible trailer player - 60% height with 5% zoom and smooth fade */}
{settings?.showTrailers && trailerUrl && !trailerLoading && !trailerError && trailerPreloaded && (
{settings?.showTrailers && trailerSource?.videoUrl && !trailerLoading && !trailerError && trailerPreloaded && (
<Animated.View style={[trailerContainerStyle, trailerParallaxStyle]}>
<Animated.View style={trailerVideoStyle}>
<TrailerPlayer
key={`visible-${trailerUrl}`}
key={`visible-${trailerSource.videoUrl}-${trailerSource.audioUrl ?? 'no-audio'}`}
ref={trailerVideoRef}
trailerUrl={trailerUrl}
trailerUrl={trailerSource.videoUrl}
audioUrl={trailerSource.audioUrl}
autoPlay={!trailerShouldBePaused}
muted={trailerMuted}
style={StyleSheet.absoluteFillObject}
@ -1168,7 +1170,7 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
</View>
{/* Trailer control buttons (unmute and fullscreen) */}
{settings?.showTrailers && trailerReady && trailerUrl && (
{settings?.showTrailers && trailerReady && trailerSource?.videoUrl && (
<Animated.View style={{
position: 'absolute',
top: (Platform.OS === 'android' ? 60 : 70) + insets.top,

View file

@ -54,7 +54,7 @@ import { useTrailer } from '../../contexts/TrailerContext';
import { useTranslation } from 'react-i18next';
import { logger } from '../../utils/logger';
import { TMDBService } from '../../services/tmdbService';
import TrailerService from '../../services/trailerService';
import TrailerService, { TrailerPlaybackSource } from '../../services/trailerService';
import TrailerPlayer from '../video/TrailerPlayer';
import { HERO_HEIGHT, SCREEN_WIDTH as width, IS_TABLET as isTablet } from '../../constants/dimensions';
@ -878,7 +878,7 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
// Image loading state with optimized management
const [imageError, setImageError] = useState(false);
const [imageLoaded, setImageLoaded] = useState(false);
const [trailerUrl, setTrailerUrl] = useState<string | null>(null);
const [trailerSource, setTrailerSource] = useState<TrailerPlaybackSource | null>(null);
const [trailerLoading, setTrailerLoading] = useState(false);
const [trailerError, setTrailerError] = useState(false);
// Use persistent setting instead of local state
@ -1188,12 +1188,12 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
logger.info('HeroSection', `Extracting stream for videoId: ${pick.key} (${metadata.name})`);
const url = await TrailerService.getTrailerFromVideoId(pick.key, metadata.name);
const source = await TrailerService.getTrailerPlaybackSourceFromVideoId(pick.key, metadata.name);
if (!alive) return;
if (url) {
setTrailerUrl(url);
if (source) {
setTrailerSource(source);
logger.info('HeroSection', `Trailer loaded for ${metadata.name}`);
} else {
logger.info('HeroSection', `No stream extracted for ${metadata.name}`);
@ -1508,7 +1508,7 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
try {
setTrailerReady(false);
setTrailerPreloaded(false);
setTrailerUrl(null);
setTrailerSource(null);
trailerOpacity.value = 0;
thumbnailOpacity.value = 1;
} catch (_e) { }
@ -1614,14 +1614,15 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
)}
{/* Single trailer player - starts hidden (opacity 0), fades in when ready */}
{shouldLoadSecondaryData && settings?.showTrailers && trailerUrl && !trailerLoading && !trailerError && (
{shouldLoadSecondaryData && settings?.showTrailers && trailerSource?.videoUrl && !trailerLoading && !trailerError && (
<Animated.View style={[staticStyles.absoluteFill, {
opacity: trailerOpacity
}, trailerParallaxStyle]}>
<TrailerPlayer
key={`trailer-${trailerUrl}`}
key={`trailer-${trailerSource.videoUrl}-${trailerSource.audioUrl ?? 'no-audio'}`}
ref={trailerVideoRef}
trailerUrl={trailerUrl}
trailerUrl={trailerSource.videoUrl}
audioUrl={trailerSource.audioUrl}
autoPlay={globalTrailerPlaying}
muted={trailerMuted}
style={staticStyles.absoluteFill}
@ -1641,7 +1642,7 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
)}
{/* Trailer control buttons (unmute and fullscreen) */}
{settings?.showTrailers && trailerReady && trailerUrl && (
{settings?.showTrailers && trailerReady && trailerSource?.videoUrl && (
<Animated.View style={{
position: 'absolute',
top: Platform.OS === 'android' ? 40 : 50,
@ -1750,7 +1751,7 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
)}
{/* AI Chat button (when trailers are disabled) */}
{settings?.aiChatEnabled && !(settings?.showTrailers && trailerReady && trailerUrl) && (
{settings?.aiChatEnabled && !(settings?.showTrailers && trailerReady && trailerSource?.videoUrl) && (
<Animated.View style={{
position: 'absolute',
top: Platform.OS === 'android' ? 40 : 50,

View file

@ -14,7 +14,7 @@ import { useTranslation } from 'react-i18next';
import { useTheme } from '../../contexts/ThemeContext';
import { useTrailer } from '../../contexts/TrailerContext';
import { logger } from '../../utils/logger';
import TrailerService from '../../services/trailerService';
import TrailerService, { TrailerPlaybackSource } from '../../services/trailerService';
import Video, { VideoRef, OnLoadData, OnProgressData } from 'react-native-video';
const { width, height } = Dimensions.get('window');
@ -38,6 +38,14 @@ interface TrailerModalProps {
contentTitle: string;
}
function isUnsupportedIosMediaFormat(error: any): boolean {
if (Platform.OS !== 'ios') return false;
const errorCode = error?.error?.code;
const errorDomain = error?.error?.domain;
return errorDomain === 'AVFoundationErrorDomain' && errorCode === -11828;
}
const TrailerModal: React.FC<TrailerModalProps> = memo(({
visible,
onClose,
@ -67,7 +75,7 @@ const TrailerModal: React.FC<TrailerModalProps> = memo(({
}, [t]);
const videoRef = React.useRef<VideoRef>(null);
const [trailerUrl, setTrailerUrl] = useState<string | null>(null);
const [playbackSource, setPlaybackSource] = useState<TrailerPlaybackSource | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [isPlaying, setIsPlaying] = useState(false);
@ -79,7 +87,7 @@ const TrailerModal: React.FC<TrailerModalProps> = memo(({
loadTrailer();
} else {
// Reset state when modal closes
setTrailerUrl(null);
setPlaybackSource(null);
setLoading(false);
setError(null);
setIsPlaying(false);
@ -87,7 +95,7 @@ const TrailerModal: React.FC<TrailerModalProps> = memo(({
}
}, [visible, trailer]);
const loadTrailer = useCallback(async () => {
const loadTrailer = useCallback(async (resetRetryCount = true) => {
if (!trailer) return;
// Pause hero section trailer when modal opens
@ -100,8 +108,10 @@ const TrailerModal: React.FC<TrailerModalProps> = memo(({
setLoading(true);
setError(null);
setTrailerUrl(null);
setRetryCount(0); // Reset retry count when starting fresh load
setPlaybackSource(null);
if (resetRetryCount) {
setRetryCount(0);
}
try {
const youtubeUrl = `https://www.youtube.com/watch?v=${trailer.key}`;
@ -109,14 +119,14 @@ const TrailerModal: React.FC<TrailerModalProps> = memo(({
logger.info('TrailerModal', `Loading trailer: ${trailer.name} (${youtubeUrl})`);
// Use the direct YouTube URL method - much more efficient!
const directUrl = await TrailerService.getTrailerFromYouTubeUrl(
const source = await TrailerService.getTrailerPlaybackSourceFromYouTubeUrl(
youtubeUrl,
`${contentTitle} - ${trailer.name}`,
new Date(trailer.published_at).getFullYear().toString()
);
if (directUrl) {
setTrailerUrl(directUrl);
if (source) {
setPlaybackSource(source);
setIsPlaying(true);
logger.info('TrailerModal', `Successfully loaded direct trailer URL for: ${trailer.name}`);
} else {
@ -159,12 +169,20 @@ const TrailerModal: React.FC<TrailerModalProps> = memo(({
const handleVideoError = useCallback((error: any) => {
logger.error('TrailerModal', 'Video error:', error);
if (isUnsupportedIosMediaFormat(error)) {
logger.error('TrailerModal', 'Unsupported iOS trailer format:', error);
setError('This trailer format is not supported on iOS.');
setLoading(false);
setIsPlaying(false);
return;
}
if (retryCount < 2) {
logger.info('TrailerModal', `Re-extracting trailer (attempt ${retryCount + 1}/2)`);
setRetryCount(prev => prev + 1);
// Invalidate cache so loadTrailer gets a fresh URL, not the same bad one
if (trailer?.key) TrailerService.invalidateCache(trailer.key);
loadTrailer();
loadTrailer(false);
return;
}
@ -242,7 +260,9 @@ const TrailerModal: React.FC<TrailerModalProps> = memo(({
</Text>
<TouchableOpacity
style={[styles.retryButton, { backgroundColor: currentTheme.colors.primary }]}
onPress={loadTrailer}
onPress={() => {
void loadTrailer(true);
}}
>
<Text style={styles.retryButtonText}>{t('common.try_again')}</Text>
</TouchableOpacity>
@ -250,21 +270,28 @@ const TrailerModal: React.FC<TrailerModalProps> = memo(({
)}
{/* Render the Video as soon as we have a URL; keep spinner overlay until onLoad */}
{trailerUrl && !error && (
{playbackSource?.videoUrl && !error && (
<View style={styles.playerWrapper}>
<Video
ref={videoRef}
source={(() => {
const lower = (trailerUrl || '').toLowerCase();
const lower = playbackSource.videoUrl.toLowerCase();
const looksLikeHls = /\.m3u8(\b|$)/.test(lower) || /hls|playlist|m3u/.test(lower);
if (Platform.OS === 'android') {
const headers = { 'User-Agent': 'Nuvio/1.0 (Android)' };
if (looksLikeHls) {
return { uri: trailerUrl, type: 'm3u8', headers } as any;
return { uri: playbackSource.videoUrl, type: 'm3u8', headers } as any;
}
return { uri: trailerUrl, headers } as any;
return {
uri: playbackSource.videoUrl,
headers,
...(playbackSource.audioUrl ? { audioUri: playbackSource.audioUrl } : null),
} as any;
}
return { uri: trailerUrl } as any;
return {
uri: playbackSource.videoUrl,
...(playbackSource.audioUrl ? { audioUri: playbackSource.audioUrl } : null),
} as any;
})()}
style={styles.player}
controls={true}

View file

@ -28,6 +28,7 @@ const isTablet = width >= 768;
interface TrailerPlayerProps {
trailerUrl: string;
audioUrl?: string | null;
autoPlay?: boolean;
muted?: boolean;
onLoadStart?: () => void;
@ -46,6 +47,7 @@ interface TrailerPlayerProps {
const TrailerPlayer = React.forwardRef<any, TrailerPlayerProps>(({
trailerUrl,
audioUrl,
autoPlay = true,
muted = true,
onLoadStart,
@ -381,9 +383,16 @@ const TrailerPlayer = React.forwardRef<any, TrailerPlayerProps>(({
if (looksLikeHls) {
return { uri: trailerUrl, type: 'm3u8', headers: androidHeaders } as any;
}
return { uri: trailerUrl, headers: androidHeaders } as any;
return {
uri: trailerUrl,
headers: androidHeaders,
...(audioUrl ? { audioUri: audioUrl } : null),
} as any;
}
return { uri: trailerUrl } as any;
return {
uri: trailerUrl,
...(audioUrl ? { audioUri: audioUrl } : null),
} as any;
})()}
style={styles.video}
resizeMode="cover"

View file

@ -472,15 +472,12 @@ const DownloadsScreen: React.FC = () => {
case 'infuse':
externalPlayerUrls = [
`infuse://x-callback-url/play?url=${streamUrl}`,
`infuse://play?url=${streamUrl}`,
`infuse://${streamUrl}`
];
break;
case 'vidhub':
externalPlayerUrls = [
`vidhub://play?url=${streamUrl}`,
`vidhub://${streamUrl}`
`open-vidhub://x-callback-url/open?url=${streamUrl}`,
];
break;

View file

@ -493,12 +493,12 @@ export const useStreamsScreen = () => {
case 'infuse':
externalPlayerUrls = [
`infuse://x-callback-url/play?url=${streamUrl}`,
`infuse://play?url=${streamUrl}`,
`infuse://${streamUrl}`,
];
break;
case 'vidhub':
externalPlayerUrls = [`vidhub://play?url=${streamUrl}`, `vidhub://${streamUrl}`];
externalPlayerUrls = [
`open-vidhub://x-callback-url/open?url=${streamUrl}`,
];
break;
default:
navigateToPlayer(stream);

View file

@ -1,6 +1,6 @@
import { logger } from '../utils/logger';
import { Platform } from 'react-native';
import { YouTubeExtractor } from './youtubeExtractor';
import { YouTubeExtractionResult, YouTubeExtractor } from './youtubeExtractor';
export interface TrailerData {
url: string;
@ -8,8 +8,14 @@ export interface TrailerData {
year: number;
}
export interface TrailerPlaybackSource {
videoUrl: string;
audioUrl: string | null;
quality?: string;
}
interface CacheEntry {
url: string;
source: TrailerPlaybackSource;
expiresAt: number;
}
@ -31,6 +37,15 @@ export class TrailerService {
title?: string,
year?: number
): Promise<string | null> {
const source = await this.getTrailerPlaybackSourceFromVideoId(youtubeVideoId, title, year);
return source?.videoUrl ?? null;
}
static async getTrailerPlaybackSourceFromVideoId(
youtubeVideoId: string,
title?: string,
year?: number
): Promise<TrailerPlaybackSource | null> {
if (!youtubeVideoId) return null;
logger.info('TrailerService', `getTrailerFromVideoId: ${youtubeVideoId} (${title ?? '?'} ${year ?? ''})`);
@ -43,11 +58,12 @@ export class TrailerService {
try {
const platform = Platform.OS === 'android' ? 'android' : 'ios';
const url = await YouTubeExtractor.getBestStreamUrl(youtubeVideoId, platform);
if (url) {
const extracted = await YouTubeExtractor.extract(youtubeVideoId, platform);
if (extracted) {
const source = this.toPlaybackSource(extracted);
logger.info('TrailerService', `Extraction succeeded for ${youtubeVideoId}`);
this.setCache(youtubeVideoId, url);
return url;
this.setCache(youtubeVideoId, source);
return source;
}
logger.warn('TrailerService', `Extraction returned null for ${youtubeVideoId}`);
} catch (err) {
@ -66,6 +82,15 @@ export class TrailerService {
title?: string,
year?: string
): Promise<string | null> {
const source = await this.getTrailerPlaybackSourceFromYouTubeUrl(youtubeUrl, title, year);
return source?.videoUrl ?? null;
}
static async getTrailerPlaybackSourceFromYouTubeUrl(
youtubeUrl: string,
title?: string,
year?: string
): Promise<TrailerPlaybackSource | null> {
logger.info('TrailerService', `getTrailerFromYouTubeUrl: ${youtubeUrl}`);
const videoId = YouTubeExtractor.parseVideoId(youtubeUrl);
@ -74,7 +99,7 @@ export class TrailerService {
return null;
}
return this.getTrailerFromVideoId(
return this.getTrailerPlaybackSourceFromVideoId(
videoId,
title,
year ? parseInt(year, 10) : undefined
@ -135,7 +160,7 @@ export class TrailerService {
// Private — cache
// ---------------------------------------------------------------------------
private static getCached(key: string): string | null {
private static getCached(key: string): TrailerPlaybackSource | null {
const entry = this.urlCache.get(key);
if (!entry) return null;
if (Date.now() > entry.expiresAt) {
@ -144,9 +169,9 @@ export class TrailerService {
}
// Check the URL's own CDN expiry — googlevideo.com URLs carry an `expire`
// param (Unix timestamp). Treat as stale if it expires within 2 minutes.
if (entry.url.includes('googlevideo.com')) {
if (entry.source.videoUrl.includes('googlevideo.com')) {
try {
const u = new URL(entry.url);
const u = new URL(entry.source.videoUrl);
const expire = u.searchParams.get('expire');
if (expire) {
const expiresAt = parseInt(expire, 10) * 1000;
@ -158,16 +183,24 @@ export class TrailerService {
}
} catch { /* ignore */ }
}
return entry.url;
return entry.source;
}
private static setCache(key: string, url: string): void {
this.urlCache.set(key, { url, expiresAt: Date.now() + this.CACHE_TTL_MS });
private static setCache(key: string, source: TrailerPlaybackSource): void {
this.urlCache.set(key, { source, expiresAt: Date.now() + this.CACHE_TTL_MS });
if (this.urlCache.size > 100) {
const oldest = this.urlCache.keys().next().value;
if (oldest) this.urlCache.delete(oldest);
}
}
private static toPlaybackSource(extracted: YouTubeExtractionResult): TrailerPlaybackSource {
return {
videoUrl: extracted.videoUrl,
audioUrl: extracted.audioUrl,
quality: extracted.quality,
};
}
}
export default TrailerService;

View file

@ -77,7 +77,6 @@ const DEFAULT_HEADERS: Record<string, string> = {
'user-agent': DEFAULT_USER_AGENT,
};
const PREFERRED_ADAPTIVE_CLIENT = 'android_vr';
const REQUEST_TIMEOUT_MS = 6000; // player API + HLS manifest requests
const WATCH_PAGE_TIMEOUT_MS = 3000; // watch page scrape — best-effort only
const MAX_RETRIES = 2; // retry extraction up to 2 times on total failure
@ -182,6 +181,12 @@ function getMimeBase(mimeType?: string): string {
return (mimeType ?? '').split(';')[0].trim();
}
function getCodecs(mimeType?: string): string[] {
const match = (mimeType ?? '').match(/codecs="([^"]+)"/i);
if (!match) return [];
return match[1].split(',').map(codec => codec.trim().toLowerCase()).filter(Boolean);
}
function getExt(mimeType?: string): 'mp4' | 'webm' | 'm4a' | 'other' {
const base = getMimeBase(mimeType);
if (base === 'video/mp4' || base === 'audio/mp4') return 'mp4';
@ -260,16 +265,6 @@ async function validateUrl(url: string, userAgent: string): Promise<boolean> {
}
}
// ---------------------------------------------------------------------------
// android_vr preferred selection — only fall back to other clients if
// android_vr returned zero formats (likely PO token required for others)
// ---------------------------------------------------------------------------
function filterPreferAndroidVr(items: StreamCandidate[]): StreamCandidate[] {
const fromVr = items.filter(c => c.client === 'android_vr');
return fromVr.length > 0 ? fromVr : items;
}
function sortCandidates(items: StreamCandidate[]): StreamCandidate[] {
return [...items].sort((a, b) => {
if (b.score !== a.score) return b.score - a.score;
@ -279,13 +274,39 @@ function sortCandidates(items: StreamCandidate[]): StreamCandidate[] {
});
}
function pickBestForClient(
async function findBestValidatedCandidate(
items: StreamCandidate[],
preferredClient: string,
): StreamCandidate | null {
const fromPreferred = items.filter(c => c.client === preferredClient);
const pool = fromPreferred.length > 0 ? fromPreferred : items;
return sortCandidates(pool)[0] ?? null;
userAgent: string,
label: string,
): Promise<StreamCandidate | null> {
for (const candidate of sortCandidates(items)) {
const valid = await validateUrl(candidate.url, userAgent);
if (valid) return candidate;
logger.warn('YouTubeExtractor', `${label} URL invalid, trying next candidate`);
}
return null;
}
function getHlsQualityScore(variant: HlsVariant): number {
return variant.height * 1_000_000_000 + variant.bandwidth;
}
function isIosCompatibleSeparateVideo(candidate: StreamCandidate): boolean {
if (getMimeBase(candidate.mimeType) !== 'video/mp4') return false;
const codecs = getCodecs(candidate.mimeType);
if (codecs.length === 0) return true;
return codecs.some(codec =>
codec.startsWith('avc1') ||
codec.startsWith('hvc1') ||
codec.startsWith('hev1'),
);
}
function isIosCompatibleSeparateAudio(candidate: StreamCandidate): boolean {
if (getMimeBase(candidate.mimeType) !== 'audio/mp4') return false;
const codecs = getCodecs(candidate.mimeType);
if (codecs.length === 0) return true;
return codecs.some(codec => codec.startsWith('mp4a'));
}
// ---------------------------------------------------------------------------
@ -580,12 +601,8 @@ export class YouTubeExtractor {
* Matches the Kotlin InAppYouTubeExtractor approach:
* 1. Fetch watch page for dynamic API key + visitor data
* 2. Try ALL clients, collect formats from all that succeed
* 3. Pick best HLS variant (by resolution/bandwidth) as primary
* 4. Fall back to best progressive (muxed) if no HLS
*
* Note: Unlike the Kotlin version, we do not return separate videoUrl/audioUrl
* for adaptive streams react-native-video cannot merge two sources. HLS
* provides the best quality without needing a separate audio track.
* 3. Prefer separate adaptive video+audio when available
* 4. Fall back to HLS/progressive muxed sources
*/
static async extract(
videoIdOrUrl: string,
@ -650,63 +667,140 @@ export class YouTubeExtractor {
}
}
// Prefer android_vr formats exclusively — other clients may require PO tokens
// and return URLs that 403 at the CDN level during playback
const preferredProgressive = sortCandidates(filterPreferAndroidVr(progressive));
const bestAdaptiveVideo = pickBestForClient(adaptiveVideo, PREFERRED_ADAPTIVE_CLIENT);
const bestAdaptiveAudio = pickBestForClient(adaptiveAudio, PREFERRED_ADAPTIVE_CLIENT);
const progressiveCandidates = sortCandidates(progressive);
const adaptiveVideoCandidates = sortCandidates(adaptiveVideo);
const adaptiveAudioCandidates = sortCandidates(adaptiveAudio);
const compatibleAdaptiveVideoCandidates =
effectivePlatform === 'ios'
? adaptiveVideoCandidates.filter(isIosCompatibleSeparateVideo)
: adaptiveVideoCandidates;
const compatibleAdaptiveAudioCandidates =
effectivePlatform === 'ios'
? adaptiveAudioCandidates.filter(isIosCompatibleSeparateAudio)
: adaptiveAudioCandidates;
if (bestHls) logger.info('YouTubeExtractor', `Best HLS: ${bestHls.height}p ${bestHls.bandwidth}bps`);
if (preferredProgressive[0]) logger.info('YouTubeExtractor', `Best progressive: ${preferredProgressive[0].height}p client=${preferredProgressive[0].client}`);
if (bestAdaptiveVideo) logger.info('YouTubeExtractor', `Best adaptive video: ${bestAdaptiveVideo.height}p client=${bestAdaptiveVideo.client}`);
if (bestAdaptiveAudio) logger.info('YouTubeExtractor', `Best adaptive audio: ${bestAdaptiveAudio.bitrate}bps client=${bestAdaptiveAudio.client}`);
if (progressiveCandidates[0]) logger.info('YouTubeExtractor', `Best progressive: ${progressiveCandidates[0].height}p client=${progressiveCandidates[0].client}`);
if (adaptiveVideoCandidates[0]) logger.info('YouTubeExtractor', `Best adaptive video: ${adaptiveVideoCandidates[0].height}p client=${adaptiveVideoCandidates[0].client}`);
if (adaptiveAudioCandidates[0]) logger.info('YouTubeExtractor', `Best adaptive audio: ${adaptiveAudioCandidates[0].bitrate}bps client=${adaptiveAudioCandidates[0].client}`);
if (effectivePlatform === 'ios') {
if (compatibleAdaptiveVideoCandidates[0]) {
logger.info(
'YouTubeExtractor',
`Best iOS-compatible adaptive video: ${compatibleAdaptiveVideoCandidates[0].height}p client=${compatibleAdaptiveVideoCandidates[0].client} mime=${compatibleAdaptiveVideoCandidates[0].mimeType}`
);
}
if (compatibleAdaptiveAudioCandidates[0]) {
logger.info(
'YouTubeExtractor',
`Best iOS-compatible adaptive audio: ${compatibleAdaptiveAudioCandidates[0].bitrate}bps client=${compatibleAdaptiveAudioCandidates[0].client} mime=${compatibleAdaptiveAudioCandidates[0].mimeType}`
);
}
}
// VR client user agent used for CDN URL validation
const vrUserAgent = CLIENTS.find(c => c.key === 'android_vr')!.userAgent;
// Step 4: select final source with URL validation
// Priority: HLS > progressive muxed
// HLS manifests don't need validation — they're not CDN segment URLs
if (bestHls) {
// Return the specific best variant URL, not the master playlist.
// Master playlist lets the player pick quality adaptively (often starts low).
// Pinning to the best variant ensures consistent high quality playback.
logger.info('YouTubeExtractor', `Using HLS variant: ${summarizeUrl(bestHls.url)} ${bestHls.height}p`);
// Step 4: validate the best candidates per source type, then pick the
// highest-quality playable result.
const validatedAdaptiveVideo = await findBestValidatedCandidate(
compatibleAdaptiveVideoCandidates,
vrUserAgent,
'Adaptive video',
);
const validatedAdaptiveAudio = await findBestValidatedCandidate(
compatibleAdaptiveAudioCandidates,
vrUserAgent,
'Adaptive audio',
);
const validatedProgressive = await findBestValidatedCandidate(
progressiveCandidates,
vrUserAgent,
'Progressive',
);
const adaptivePlayback =
validatedAdaptiveVideo && validatedAdaptiveAudio
? {
type: 'adaptive' as const,
videoUrl: validatedAdaptiveVideo.url,
audioUrl: validatedAdaptiveAudio.url,
quality: `${validatedAdaptiveVideo.height}p`,
score: validatedAdaptiveVideo.score,
height: validatedAdaptiveVideo.height,
audioBitrate: validatedAdaptiveAudio.bitrate,
videoClient: validatedAdaptiveVideo.client,
videoMimeType: validatedAdaptiveVideo.mimeType,
videoExt: validatedAdaptiveVideo.ext,
audioClient: validatedAdaptiveAudio.client,
audioMimeType: validatedAdaptiveAudio.mimeType,
audioExt: validatedAdaptiveAudio.ext,
}
: null;
const progressivePlayback = validatedProgressive
? {
type: 'progressive' as const,
videoUrl: validatedProgressive.url,
audioUrl: null,
quality: `${validatedProgressive.height}p`,
score: validatedProgressive.score,
height: validatedProgressive.height,
}
: null;
const hlsPlayback = bestHls
? {
type: 'hls' as const,
videoUrl: bestHls.manifestUrl,
audioUrl: null,
quality: `${bestHls.height}p`,
score: getHlsQualityScore(bestHls),
height: bestHls.height,
bandwidth: bestHls.bandwidth,
}
: null;
const bestPlayable = [adaptivePlayback, progressivePlayback, hlsPlayback]
.filter((candidate): candidate is NonNullable<typeof candidate> => candidate !== null)
.sort((a, b) => b.score - a.score)[0] ?? null;
if (bestPlayable) {
if (bestPlayable.type === 'adaptive') {
logger.info(
'YouTubeExtractor',
`Using separate adaptive streams: video=${bestPlayable.height}p ` +
`videoClient=${bestPlayable.videoClient} videoMime=${bestPlayable.videoMimeType} videoExt=${bestPlayable.videoExt} ` +
`audio=${bestPlayable.audioBitrate}bps audioClient=${bestPlayable.audioClient} ` +
`audioMime=${bestPlayable.audioMimeType} audioExt=${bestPlayable.audioExt}`
);
} else if (bestPlayable.type === 'progressive') {
logger.info(
'YouTubeExtractor',
`Using progressive: ${summarizeUrl(bestPlayable.videoUrl)} ${bestPlayable.height}p`
);
} else {
logger.info(
'YouTubeExtractor',
`Using HLS manifest: ${summarizeUrl(bestPlayable.videoUrl)} ${bestPlayable.height}p`
);
}
return {
videoUrl: bestHls.url,
audioUrl: null,
quality: `${bestHls.height}p`,
videoUrl: bestPlayable.videoUrl,
audioUrl: bestPlayable.audioUrl,
quality: bestPlayable.quality,
videoId,
};
}
// Validate progressive candidates in order, return first valid one
for (const candidate of preferredProgressive) {
const valid = await validateUrl(candidate.url, vrUserAgent);
if (valid) {
logger.info('YouTubeExtractor', `Using progressive: ${summarizeUrl(candidate.url)} ${candidate.height}p`);
return {
videoUrl: candidate.url,
audioUrl: null,
quality: `${candidate.height}p`,
videoId,
};
}
logger.warn('YouTubeExtractor', `Progressive URL invalid, trying next candidate`);
}
// Last resort: video-only adaptive (no audio, but beats nothing)
if (bestAdaptiveVideo) {
const valid = await validateUrl(bestAdaptiveVideo.url, vrUserAgent);
if (valid) {
logger.warn('YouTubeExtractor', `Using video-only adaptive (no audio): ${bestAdaptiveVideo.height}p`);
return {
videoUrl: bestAdaptiveVideo.url,
audioUrl: null,
quality: `${bestAdaptiveVideo.height}p`,
videoId,
};
}
if (validatedAdaptiveVideo) {
logger.warn('YouTubeExtractor', `Using video-only adaptive fallback (no audio): ${validatedAdaptiveVideo.height}p`);
return {
videoUrl: validatedAdaptiveVideo.url,
audioUrl: null,
quality: `${validatedAdaptiveVideo.height}p`,
videoId,
};
}
logger.warn('YouTubeExtractor', `No playable source for videoId=${videoId}`);