From 879af0a0c2497d10da1cc43510de889c5e713b94 Mon Sep 17 00:00:00 2001 From: CK <86251706+altCK129@users.noreply.github.com> Date: Thu, 5 Mar 2026 13:43:08 +0530 Subject: [PATCH 01/35] Create build.yml --- .github/workflows/build.yml | 95 +++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..6dac6fda --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,95 @@ +name: Build Android APK + +on: + workflow_dispatch: + inputs: + build_type: + description: 'Build type' + required: true + default: 'release' + type: choice + options: + - release + - debug + commit_sha: + description: 'Commit SHA to build from (leave empty for latest)' + required: false + type: string + notes: + description: 'Notes (optional)' + required: false + type: string + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ inputs.commit_sha || github.sha }} + + - name: Print build info + run: | + echo "Building commit: $(git rev-parse HEAD)" + echo "Commit message: $(git log -1 --pretty=%B)" + echo "Build type: ${{ inputs.build_type }}" + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'npm' + + - name: Setup Java 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: Install NDK r29 + run: | + echo "y" | sdkmanager "ndk;29.0.14206865" + + - name: Create .env file (debug defaults) + run: | + cp .env.example .env + sed -i 's/your_[^ ]*/debug_placeholder/g' .env + + - name: Install dependencies + run: npm install --legacy-peer-deps + + - name: Apply patches + run: npx patch-package + + + - name: Make gradlew executable + run: chmod +x android/gradlew + + - name: Build ${{ inputs.build_type }} APK + env: + NODE_ENV: production + SENTRY_DISABLE_AUTO_UPLOAD: true + run: | + cd android + ./gradlew clean + if [ "${{ inputs.build_type }}" = "release" ]; then + ./gradlew assembleRelease --no-daemon --stacktrace --rerun-tasks + else + ./gradlew assembleDebug --no-daemon --stacktrace --rerun-tasks + fi + + - name: List APK outputs + run: find android/app/build/outputs/apk -name "*.apk" | sort + + - name: Upload APK as artifact + uses: actions/upload-artifact@v4 + with: + name: nuvio-${{ inputs.build_type }}-${{ inputs.commit_sha != '' && inputs.commit_sha || github.sha }}-run${{ github.run_number }} + path: android/app/build/outputs/apk/${{ inputs.build_type }}/*.apk + retention-days: 30 + From 601194b93fd78863c01f45cf0efab62d11abf66b Mon Sep 17 00:00:00 2001 From: CK <86251706+altCK129@users.noreply.github.com> Date: Thu, 5 Mar 2026 13:46:02 +0530 Subject: [PATCH 02/35] Update youtubeExtractor.ts 1 --- src/services/youtubeExtractor.ts | 98 +++++++++++++++++++------------- 1 file changed, 60 insertions(+), 38 deletions(-) diff --git a/src/services/youtubeExtractor.ts b/src/services/youtubeExtractor.ts index c01aeda3..b109cc0c 100644 --- a/src/services/youtubeExtractor.ts +++ b/src/services/youtubeExtractor.ts @@ -68,52 +68,67 @@ export interface YouTubeExtractionResult { // Constants // --------------------------------------------------------------------------- -// Innertube client configs — we use Android (no cipher, direct URLs) -// and web as fallback (may need cipher decode) -const INNERTUBE_API_KEY = 'AIzaSyA8ggJvXiQHQFN-YMEoM30s0s3RlxEYJuA'; +// Innertube client configs. +// Note: ?key= param was deprecated by YouTube in mid-2023 and is no longer sent. +// +// IMPORTANT: As of late 2024, YouTube requires Proof of Origin (PO) tokens for +// most clients (ANDROID, IOS, WEB). Without a PO token, the player API returns +// format URLs but segment fetches get HTTP 403. The ANDROID_VR client is currently +// the only client that bypasses PO token requirements and gives full format access +// without authentication. Keep it first in the client list. +// +// Reference: https://github.com/yt-dlp/yt-dlp/wiki/PO-Token-Guide const INNERTUBE_URL = 'https://www.youtube.com/youtubei/v1/player'; -// Android client gives direct URLs without cipher obfuscation +// ANDROID_VR (Oculus Quest) — bypasses PO token requirement, full format access. +// This is the primary client. clientVersion from yt-dlp as of 2025. +const ANDROID_VR_CLIENT_CONTEXT = { + client: { + clientName: 'ANDROID_VR', + clientVersion: '1.60.19', + deviceMake: 'Oculus', + deviceModel: 'Quest 3', + androidSdkVersion: 32, + userAgent: + 'com.google.android.apps.youtube.vr.oculus/1.60.19 (Linux; U; Android 12L; eureka-user Build/SQ3A.220605.009.A1) gzip', + hl: 'en', + gl: 'US', + }, +}; + +// ANDROID_SDKLESS — secondary fallback, no PO token needed, updated version. +// Works for most non-age-restricted content. const ANDROID_CLIENT_CONTEXT = { client: { clientName: 'ANDROID', - clientVersion: '19.09.37', + clientVersion: '20.10.38', androidSdkVersion: 30, + osName: 'Android', + osVersion: '11', userAgent: - 'com.google.android.youtube/19.09.37 (Linux; U; Android 11) gzip', + 'com.google.android.youtube/20.10.38 (Linux; U; Android 11) gzip', hl: 'en', gl: 'US', }, }; -// iOS client as secondary fallback -const IOS_CLIENT_CONTEXT = { +// TV client — no PO token needed, good format availability. +const TV_CLIENT_CONTEXT = { client: { - clientName: 'IOS', - clientVersion: '19.09.3', - deviceModel: 'iPhone14,3', + clientName: 'TVHTML5', + clientVersion: '7.20250422.19.00', userAgent: - 'com.google.ios.youtube/19.09.3 (iPhone14,3; U; CPU iPhone OS 15_6 like Mac OS X)', + 'Mozilla/5.0 (SMART-TV; Linux; Tizen 6.0) AppleWebKit/538.1 (KHTML, like Gecko) Version/6.0 TV Safari/538.1', hl: 'en', gl: 'US', }, }; -// TV Embedded client — works for age-restricted / embed-allowed content -const TVHTML5_EMBEDDED_CONTEXT = { - client: { - clientName: 'TVHTML5_SIMPLY_EMBEDDED_PLAYER', - clientVersion: '2.0', - hl: 'en', - gl: 'US', - }, -}; - -// Web Embedded client — good fallback for content that rejects app clients +// Web Embedded — fallback for embeddable content, may need PO token for segments. const WEB_EMBEDDED_CONTEXT = { client: { clientName: 'WEB_EMBEDDED_PLAYER', - clientVersion: '2.20240726.00.00', + clientVersion: '2.20250422.01.00', hl: 'en', gl: 'US', }, @@ -359,7 +374,8 @@ async function writeDashManifestToFile( async function fetchPlayerResponse( videoId: string, context: object, - userAgent: string + userAgent: string, + clientNameId: string = '3' ): Promise { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS); @@ -373,13 +389,13 @@ async function fetchPlayerResponse( }; const response = await fetch( - `${INNERTUBE_URL}?key=${INNERTUBE_API_KEY}&prettyPrint=false`, + `${INNERTUBE_URL}?prettyPrint=false`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'User-Agent': userAgent, - 'X-YouTube-Client-Name': '3', + 'X-YouTube-Client-Name': clientNameId, 'Origin': 'https://www.youtube.com', 'Referer': `https://www.youtube.com/watch?v=${videoId}`, }, @@ -500,24 +516,30 @@ export class YouTubeExtractor { logger.info('YouTubeExtractor', `Extracting for videoId=${videoId} platform=${platform ?? 'unknown'}`); - const clients: Array<{ context: object; userAgent: string; name: string }> = [ + // Client order matters: ANDROID_VR first because it bypasses PO token requirements. + // Other clients may return 403 on segment fetch even if player API succeeds. + const clients: Array<{ context: object; userAgent: string; name: string; clientNameId: string }> = [ + { + name: 'ANDROID_VR', + clientNameId: '28', + context: ANDROID_VR_CLIENT_CONTEXT, + userAgent: 'com.google.android.apps.youtube.vr.oculus/1.60.19 (Linux; U; Android 12L; eureka-user Build/SQ3A.220605.009.A1) gzip', + }, { name: 'ANDROID', + clientNameId: '3', context: ANDROID_CLIENT_CONTEXT, - userAgent: 'com.google.android.youtube/19.09.37 (Linux; U; Android 11) gzip', + userAgent: 'com.google.android.youtube/20.10.38 (Linux; U; Android 11) gzip', }, { - name: 'IOS', - context: IOS_CLIENT_CONTEXT, - userAgent: 'com.google.ios.youtube/19.09.3 (iPhone14,3; U; CPU iPhone OS 15_6 like Mac OS X)', - }, - { - name: 'TVHTML5_EMBEDDED', - context: TVHTML5_EMBEDDED_CONTEXT, - userAgent: 'Mozilla/5.0 (SMART-TV; Linux; Tizen 6.0)', + name: 'TV', + clientNameId: '7', + context: TV_CLIENT_CONTEXT, + userAgent: 'Mozilla/5.0 (SMART-TV; Linux; Tizen 6.0) AppleWebKit/538.1 (KHTML, like Gecko) Version/6.0 TV Safari/538.1', }, { name: 'WEB_EMBEDDED', + clientNameId: '56', context: WEB_EMBEDDED_CONTEXT, userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36', }, @@ -529,7 +551,7 @@ export class YouTubeExtractor { for (const client of clients) { logger.info('YouTubeExtractor', `Trying ${client.name} client...`); - const resp = await fetchPlayerResponse(videoId, client.context, client.userAgent); + const resp = await fetchPlayerResponse(videoId, client.context, client.userAgent, client.clientNameId); if (!resp) continue; const status = resp.playabilityStatus?.status; From 02e9aef9f29bac0c3844d313cd1d46fd5d69ec1c Mon Sep 17 00:00:00 2001 From: CK <86251706+altCK129@users.noreply.github.com> Date: Thu, 5 Mar 2026 13:47:47 +0530 Subject: [PATCH 03/35] Update AppleTVHero.tsx 2 --- src/components/home/AppleTVHero.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/home/AppleTVHero.tsx b/src/components/home/AppleTVHero.tsx index 18a1e0a1..578c9362 100644 --- a/src/components/home/AppleTVHero.tsx +++ b/src/components/home/AppleTVHero.tsx @@ -460,7 +460,7 @@ const AppleTVHero: React.FC = ({ // Fetch video list from TMDB to get the YouTube video ID const tmdbApiKey = await TMDBService.getInstance().getApiKey(); const videosRes = await fetch( - `https://api.themoviedb.org/3/${contentType}/${tmdbId}/videos?api_key=${tmdbApiKey}&language=en-US` + `https://api.themoviedb.org/3/${contentType}/${tmdbId}/videos?api_key=${tmdbApiKey}` ); if (!alive) return; From 274d2f7d0a9d936bb5f5c12637dceca5c26dfa19 Mon Sep 17 00:00:00 2001 From: CK <86251706+altCK129@users.noreply.github.com> Date: Thu, 5 Mar 2026 13:49:06 +0530 Subject: [PATCH 04/35] Update HeroSection.tsx 3 --- src/components/metadata/HeroSection.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/metadata/HeroSection.tsx b/src/components/metadata/HeroSection.tsx index 67f6221c..b3f79705 100644 --- a/src/components/metadata/HeroSection.tsx +++ b/src/components/metadata/HeroSection.tsx @@ -1155,8 +1155,9 @@ const HeroSection: React.FC = memo(({ logger.info('HeroSection', `Fetching TMDB videos for ${metadata.name} (tmdbId: ${resolvedTmdbId})`); // Fetch video list from TMDB to get the YouTube video ID + const tmdbApiKey = await TMDBService.getInstance().getApiKey(); const videosRes = await fetch( - `https://api.themoviedb.org/3/${contentType}/${resolvedTmdbId}/videos?api_key=d131017ccc6e5462a81c9304d21476de&language=en-US` + `https://api.themoviedb.org/3/${contentType}/${resolvedTmdbId}/videos?api_key=${tmdbApiKey}` ); if (!alive) return; From b2cce9484153f5fd7e56346e12a024a0d4179ef4 Mon Sep 17 00:00:00 2001 From: CK <86251706+altCK129@users.noreply.github.com> Date: Thu, 5 Mar 2026 13:50:11 +0530 Subject: [PATCH 05/35] Update TrailersSection.tsx 4 --- src/components/metadata/TrailersSection.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/metadata/TrailersSection.tsx b/src/components/metadata/TrailersSection.tsx index 7bc86baa..579a51cc 100644 --- a/src/components/metadata/TrailersSection.tsx +++ b/src/components/metadata/TrailersSection.tsx @@ -201,7 +201,7 @@ const TrailersSection: React.FC = memo(({ if (type === 'movie') { // For movies, just fetch the main videos endpoint - const videosEndpoint = `https://api.themoviedb.org/3/movie/${tmdbId}/videos?api_key=${tmdbApiKey}&language=en-US`; + const videosEndpoint = `https://api.themoviedb.org/3/movie/${tmdbId}/videos?api_key=${tmdbApiKey}`; logger.info('TrailersSection', `Fetching movie videos from: ${videosEndpoint}`); @@ -232,7 +232,7 @@ const TrailersSection: React.FC = memo(({ logger.info('TrailersSection', `TV show has ${numberOfSeasons} seasons`); // Fetch main TV show videos - const tvVideosEndpoint = `https://api.themoviedb.org/3/tv/${tmdbId}/videos?api_key=${tmdbApiKey}&language=en-US`; + const tvVideosEndpoint = `https://api.themoviedb.org/3/tv/${tmdbId}/videos?api_key=${tmdbApiKey}`; const tvResponse = await fetch(tvVideosEndpoint); if (tvResponse.ok) { @@ -251,7 +251,7 @@ const TrailersSection: React.FC = memo(({ const seasonPromises = []; for (let seasonNum = 1; seasonNum <= numberOfSeasons; seasonNum++) { seasonPromises.push( - fetch(`https://api.themoviedb.org/3/tv/${tmdbId}/season/${seasonNum}/videos?api_key=${tmdbApiKey}&language=en-US`) + fetch(`https://api.themoviedb.org/3/tv/${tmdbId}/season/${seasonNum}/videos?api_key=${tmdbApiKey}`) .then(res => res.json()) .then(data => ({ seasonNumber: seasonNum, From 2019230dfd5378aa50e164c9b9fe6cf2b4e4034b Mon Sep 17 00:00:00 2001 From: CK <86251706+altCK129@users.noreply.github.com> Date: Thu, 5 Mar 2026 13:54:56 +0530 Subject: [PATCH 06/35] Update TrailerPlayer.tsx --- src/components/video/TrailerPlayer.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/video/TrailerPlayer.tsx b/src/components/video/TrailerPlayer.tsx index cd5d9e75..7734aad8 100644 --- a/src/components/video/TrailerPlayer.tsx +++ b/src/components/video/TrailerPlayer.tsx @@ -374,12 +374,20 @@ const TrailerPlayer = React.forwardRef(({