More shaka player progress
This commit is contained in:
Generated
+520
-319
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,8 @@
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"format": "prettier --write ."
|
||||
"format": "prettier --write .",
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@capacitor/assets": "^3.0.5",
|
||||
@@ -30,6 +31,7 @@
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.45.1",
|
||||
"jsdom": "^25.0.1",
|
||||
"patch-package": "^8.0.0",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-svelte": "^3.2.7",
|
||||
"svelte": "^5.19.7",
|
||||
@@ -73,4 +75,4 @@
|
||||
"terser": "^5.34.1",
|
||||
"youtubei.js": "^12.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
diff --git a/node_modules/shaka-player/dist/shaka-player.compiled.d.ts b/node_modules/shaka-player/dist/shaka-player.compiled.d.ts
|
||||
index 0971e58..9d5c515 100644
|
||||
--- a/node_modules/shaka-player/dist/shaka-player.compiled.d.ts
|
||||
+++ b/node_modules/shaka-player/dist/shaka-player.compiled.d.ts
|
||||
@@ -5266,3 +5266,5 @@ declare namespace shaka.extern {
|
||||
declare namespace shaka.extern {
|
||||
type TransmuxerPlugin = ( ) => shaka.extern.Transmuxer ;
|
||||
}
|
||||
+
|
||||
+export default shaka;
|
||||
diff --git a/node_modules/shaka-player/dist/shaka-player.ui.d.ts b/node_modules/shaka-player/dist/shaka-player.ui.d.ts
|
||||
index 460e009..41bab00 100644
|
||||
--- a/node_modules/shaka-player/dist/shaka-player.ui.d.ts
|
||||
+++ b/node_modules/shaka-player/dist/shaka-player.ui.d.ts
|
||||
@@ -6011,3 +6011,5 @@ declare namespace shaka.extern {
|
||||
declare namespace shaka.ui.Watermark {
|
||||
type Options = { alpha : number , color : string , displayDuration : number , interval : number , position : string , size : number , skip : number , text : string , transitionDuration : number , type : string } ;
|
||||
}
|
||||
+
|
||||
+export default shaka;
|
||||
diff --git a/node_modules/shaka-player/index.d.ts b/node_modules/shaka-player/index.d.ts
|
||||
new file mode 100644
|
||||
index 0000000..79f4829
|
||||
--- /dev/null
|
||||
+++ b/node_modules/shaka-player/index.d.ts
|
||||
@@ -0,0 +1,2 @@
|
||||
+<reference path="./dist/shaka-player.compiled.d.ts" />
|
||||
+<reference path="./dist/shaka-player.ui.d.ts" />
|
||||
\ No newline at end of file
|
||||
diff --git a/node_modules/shaka-player/ui.d.ts b/node_modules/shaka-player/ui.d.ts
|
||||
new file mode 100644
|
||||
index 0000000..84a3be0
|
||||
--- /dev/null
|
||||
+++ b/node_modules/shaka-player/ui.d.ts
|
||||
@@ -0,0 +1,3 @@
|
||||
+import shaka from 'shaka-player/dist/shaka-player.ui'
|
||||
+export * from 'shaka-player/dist/shaka-player.ui'
|
||||
+export default shaka;
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
declare module 'virtual:pwa-info';
|
||||
declare module 'virtual:pwa-info';
|
||||
|
||||
@@ -1,55 +1,20 @@
|
||||
import { goto } from "$app/navigation";
|
||||
import { Capacitor } from "@capacitor/core";
|
||||
import { capacitorFetch } from "./capacitorFetch";
|
||||
|
||||
export const localProxy = 'http://localhost:3000/';
|
||||
|
||||
if (Capacitor.getPlatform() === 'android') {
|
||||
const originalFetch = window.fetch;
|
||||
|
||||
const corsProxyUrl: string = 'http://localhost:3000/';
|
||||
|
||||
window.fetch = async (requestInput: string | URL | Request, requestOptions?: RequestInit): Promise<Response> => {
|
||||
let requestUrl: string;
|
||||
|
||||
if (typeof requestInput === 'string') {
|
||||
requestUrl = requestInput;
|
||||
} else if ('url' in requestInput) {
|
||||
requestUrl = requestInput.url;
|
||||
requestOptions = {
|
||||
body: requestInput.body,
|
||||
cache: requestInput.cache,
|
||||
credentials: requestInput.credentials,
|
||||
headers: requestInput.headers,
|
||||
method: requestInput.method,
|
||||
mode: requestInput.mode,
|
||||
redirect: requestInput.redirect,
|
||||
referrer: requestInput.referrer,
|
||||
signal: requestInput.signal,
|
||||
};
|
||||
|
||||
if (requestOptions.body && requestOptions.body instanceof ReadableStream) {
|
||||
(requestOptions as RequestInit & { duplex?: 'half' | 'full'; }).duplex = 'half';
|
||||
}
|
||||
} else {
|
||||
requestUrl = requestInput.toString();
|
||||
window.fetch = async (
|
||||
requestInput: string | URL | Request, requestOptions?: RequestInit
|
||||
): Promise<Response> => {
|
||||
if (requestInput.toString().startsWith(localProxy)) {
|
||||
return originalFetch(requestInput, requestOptions);
|
||||
}
|
||||
|
||||
if (!requestUrl.startsWith(corsProxyUrl) && !requestUrl.startsWith('/') && !requestUrl.startsWith('blob:')) {
|
||||
requestInput = corsProxyUrl + requestUrl;
|
||||
}
|
||||
|
||||
// Use the original fetch with the proxied URL and options
|
||||
return originalFetch(requestInput, requestOptions);
|
||||
};
|
||||
|
||||
const currentOrigin: string = window.location.protocol + '//' + window.location.host;
|
||||
const originalXhrOpen = XMLHttpRequest.prototype.open;
|
||||
|
||||
XMLHttpRequest.prototype.open = function (...args: any[]): void {
|
||||
const targetOriginMatch = /^https?:\/\/([^\/]+)/i.exec(args[1]);
|
||||
if (targetOriginMatch && targetOriginMatch[0].toLowerCase() !== currentOrigin) {
|
||||
args[1] = corsProxyUrl + args[1];
|
||||
}
|
||||
/* @ts-ignore */
|
||||
return originalXhrOpen.apply(this, args);
|
||||
return capacitorFetch(requestInput, requestOptions);
|
||||
};
|
||||
|
||||
setTimeout(() => goto('/', { replaceState: true }), 10);
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { localProxy } from '$lib/android/http/androidRequests';
|
||||
import { getBestThumbnail } from '$lib/images';
|
||||
import { padTime, videoLength } from '$lib/time';
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
import { ScreenOrientation, type ScreenOrientationResult } from '@capacitor/screen-orientation';
|
||||
import { StatusBar, Style } from '@capacitor/status-bar';
|
||||
import { NavigationBar } from '@hugotomazi/capacitor-navigation-bar';
|
||||
import type { Page } from '@sveltejs/kit';
|
||||
import { error, type Page } from '@sveltejs/kit';
|
||||
import 'shaka-player/dist/controls.css';
|
||||
import shaka from 'shaka-player/dist/shaka-player.ui';
|
||||
import { SponsorBlock, type Category, type Segment } from 'sponsorblock-api';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { get } from 'svelte/store';
|
||||
import { deleteVideoProgress, getVideoProgress, saveVideoProgress } from '../api';
|
||||
import type { VideoPlay } from '../api/model';
|
||||
import { pullBitratePreference, type PhasedDescription } from '../misc';
|
||||
import { type PhasedDescription } from '../misc';
|
||||
import {
|
||||
authStore,
|
||||
instanceStore,
|
||||
@@ -21,10 +25,10 @@
|
||||
playerDefaultLanguage,
|
||||
playerProxyVideosStore,
|
||||
playerSavePlaybackPositionStore,
|
||||
poTokenCacheStore,
|
||||
sponsorBlockCategoriesStore,
|
||||
sponsorBlockDisplayToastStore,
|
||||
sponsorBlockStore,
|
||||
sponsorBlockTimelineStore,
|
||||
sponsorBlockUrlStore,
|
||||
synciousInstanceStore,
|
||||
synciousStore
|
||||
@@ -42,59 +46,19 @@
|
||||
let { data, audioMode = false, isEmbed = false, segments = $bindable([]) }: Props = $props();
|
||||
|
||||
let snackBarAlert = $state('');
|
||||
let playerIsLive = $state(false);
|
||||
let playerPosSet = false;
|
||||
let originalOrigination: ScreenOrientationResult | undefined;
|
||||
let sponsorBlockElements: Element[] = [];
|
||||
let watchProgressTimeout: NodeJS.Timeout;
|
||||
|
||||
function setSponsorTimeline() {
|
||||
if (get(sponsorBlockTimelineStore)) return;
|
||||
if (segments.length === 0) return;
|
||||
|
||||
const timeline = document.getElementsByClassName('vds-time-slider')[0];
|
||||
|
||||
if (sponsorBlockElements.length > 0) {
|
||||
sponsorBlockElements.forEach((barDiv) => {
|
||||
if (timeline.contains(barDiv)) {
|
||||
timeline.removeChild(barDiv);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const segmentColors = {
|
||||
sponsor: '00d400',
|
||||
selfpromo: 'ffff00',
|
||||
interaction: 'cc00ff',
|
||||
intro: '00ffff',
|
||||
outro: '0202ed',
|
||||
preview: '008fd6',
|
||||
music_offtopic: 'ff9900',
|
||||
filler: '7300FF'
|
||||
};
|
||||
|
||||
segments.forEach((segment) => {
|
||||
const startPercent = (segment.startTime / data.video.lengthSeconds) * 100;
|
||||
const endPercent = (segment.endTime / data.video.lengthSeconds) * 100;
|
||||
const widthPercent = endPercent - startPercent;
|
||||
|
||||
const barDiv = document.createElement('div');
|
||||
barDiv.classList.add('sponsorskip-bar');
|
||||
barDiv.style.left = `${startPercent}%`;
|
||||
barDiv.style.width = `${widthPercent}%`;
|
||||
barDiv.style.backgroundColor =
|
||||
segment.category in segmentColors ? `#${segmentColors[segment.category]}` : 'grey';
|
||||
|
||||
timeline.appendChild(barDiv);
|
||||
sponsorBlockElements.push(barDiv);
|
||||
});
|
||||
}
|
||||
let player: shaka.Player;
|
||||
let playerElement: HTMLMediaElement;
|
||||
|
||||
function loadTimeFromUrl(page: Page): boolean {
|
||||
if (player) {
|
||||
const timeGivenUrl = page.url.searchParams.get('time');
|
||||
if (timeGivenUrl && !isNaN(parseFloat(timeGivenUrl))) {
|
||||
player.currentTime = Number(timeGivenUrl);
|
||||
playerElement.currentTime = Number(timeGivenUrl);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -107,19 +71,83 @@
|
||||
const proxyVideos = get(playerProxyVideosStore);
|
||||
|
||||
onMount(async () => {
|
||||
shaka.polyfill.installAll();
|
||||
if (!shaka.Player.isBrowserSupported()) {
|
||||
error(400, 'Shaka not supported on your browser');
|
||||
}
|
||||
|
||||
player = new shaka.Player();
|
||||
playerElement = document.getElementById('player') as HTMLMediaElement;
|
||||
|
||||
await player.attach(playerElement);
|
||||
|
||||
const shakaUi = new shaka.ui.Overlay(
|
||||
player,
|
||||
document.getElementById('shaka-container') as HTMLElement,
|
||||
playerElement
|
||||
);
|
||||
|
||||
shakaUi.configure({
|
||||
chapter: true,
|
||||
play_pause: true,
|
||||
time_and_duration: true,
|
||||
overflowMenuButtons: ['chapter', 'cast', 'airplay', 'captions', 'quality', 'loop', 'language']
|
||||
});
|
||||
|
||||
const playerConfig = player.getConfiguration();
|
||||
const instanceDefaultBitrate = import.meta.env.VITE_DEFAULT_DASH_BITRATE
|
||||
? Number(import.meta.env.VITE_DEFAULT_DASH_BITRATE)
|
||||
: -1;
|
||||
playerConfig.abr.defaultBandwidthEstimate = instanceDefaultBitrate;
|
||||
|
||||
player.configure(playerConfig);
|
||||
|
||||
const networkingEngine = player.getNetworkingEngine();
|
||||
|
||||
if (!networkingEngine) return;
|
||||
|
||||
networkingEngine.registerRequestFilter(async (type, request) => {
|
||||
const uri = request.uris[0];
|
||||
const url = new URL(uri);
|
||||
const headers = request.headers;
|
||||
|
||||
let proxiedUrl = url.toString();
|
||||
|
||||
if (
|
||||
Capacitor.getPlatform() === 'android' &&
|
||||
(url.host.endsWith('.googlevideo.com') || url.href.includes('drm'))
|
||||
) {
|
||||
proxiedUrl = localProxy + proxiedUrl;
|
||||
}
|
||||
|
||||
if (type === shaka.net.NetworkingEngine.RequestType.SEGMENT) {
|
||||
if (url.pathname.includes('videoplayback')) {
|
||||
if (headers.Range) {
|
||||
url.searchParams.set('range', headers.Range.split('=')[1]);
|
||||
url.searchParams.set('ump', '1');
|
||||
url.searchParams.set('srfvp', '1');
|
||||
url.searchParams.set('pot', get(poTokenCacheStore));
|
||||
}
|
||||
}
|
||||
|
||||
request.method = 'POST';
|
||||
request.body = new Uint8Array([120, 0]);
|
||||
}
|
||||
|
||||
request.uris[0] = proxiedUrl;
|
||||
});
|
||||
|
||||
if (!data.video.hlsUrl) {
|
||||
playerIsLive = false;
|
||||
if (data.video.captions) {
|
||||
data.video.captions.forEach(async (caption) => {
|
||||
player.textTracks.add({
|
||||
label: caption.label,
|
||||
kind: 'captions',
|
||||
language: caption.language_code,
|
||||
// Need if captions are generated when youtube.js is being used.
|
||||
src: caption.url.startsWith('http')
|
||||
? caption.url
|
||||
: `${get(instanceStore)}${caption.url}`
|
||||
});
|
||||
player.addTextTrackAsync(
|
||||
caption.url.startsWith('http') ? caption.url : `${get(instanceStore)}${caption.url}`,
|
||||
caption.language_code,
|
||||
'captions',
|
||||
undefined,
|
||||
undefined,
|
||||
caption.label
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -141,44 +169,16 @@
|
||||
});
|
||||
|
||||
if (timestampIndex > 0) {
|
||||
player.textTracks.add({
|
||||
kind: 'chapters',
|
||||
src: URL.createObjectURL(new Blob([chapterWebVTT])),
|
||||
default: true
|
||||
});
|
||||
player.addChaptersTrack(
|
||||
URL.createObjectURL(new Blob([chapterWebVTT])),
|
||||
get(playerDefaultLanguage)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Auto save watch progress every minute.
|
||||
watchProgressTimeout = setInterval(() => savePlayerPos(), 60000);
|
||||
|
||||
player.addEventListener('provider-change', (event) => {
|
||||
const provider = event.detail as any;
|
||||
if (provider?.type === 'dash') {
|
||||
const bitrate = pullBitratePreference();
|
||||
const instanceDefaultBitrate = import.meta.env.VITE_DEFAULT_DASH_BITRATE
|
||||
? Number(import.meta.env.VITE_DEFAULT_DASH_BITRATE)
|
||||
: -1;
|
||||
provider.library = () => import('dashjs');
|
||||
provider.config = {
|
||||
streaming: {
|
||||
abr: {
|
||||
ABRStrategy: 'abrBola',
|
||||
movingAverageMethod: 'ewma',
|
||||
autoSwitchBitrate: {
|
||||
video: false,
|
||||
audio: false
|
||||
},
|
||||
initialBitrate: {
|
||||
video: bitrate === -1 ? instanceDefaultBitrate : bitrate,
|
||||
audio: bitrate === -1 ? instanceDefaultBitrate : bitrate
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
if (get(sponsorBlockStore) && get(sponsorBlockCategoriesStore)) {
|
||||
const currentCategories = get(sponsorBlockCategoriesStore);
|
||||
|
||||
@@ -193,22 +193,16 @@
|
||||
get(sponsorBlockCategoriesStore) as Category[]
|
||||
);
|
||||
|
||||
setSponsorTimeline();
|
||||
|
||||
addEventListener('resize', () => {
|
||||
setSponsorTimeline();
|
||||
});
|
||||
|
||||
player.addEventListener('time-update', (event: MediaTimeUpdateEvent) => {
|
||||
player.addEventListener('time-update', () => {
|
||||
segments.forEach((segment) => {
|
||||
if (
|
||||
event.detail.currentTime >= segment.startTime &&
|
||||
event.detail.currentTime <= segment.endTime
|
||||
playerElement.currentTime >= segment.startTime &&
|
||||
playerElement.currentTime <= segment.endTime
|
||||
) {
|
||||
if (Math.round(player.currentTime) >= Math.round(player.duration)) {
|
||||
if (Math.round(playerElement.currentTime) >= Math.round(playerElement.duration)) {
|
||||
return;
|
||||
}
|
||||
player.currentTime = segment.endTime + 1;
|
||||
playerElement.currentTime = segment.endTime + 1;
|
||||
if (!get(sponsorBlockDisplayToastStore)) {
|
||||
snackBarAlert = `${get(_)('skipping')} ${segment.category}`;
|
||||
ui('#snackbar-alert');
|
||||
@@ -220,35 +214,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
src = [{ src: data.video.dashUrl, type: 'application/dash+xml' }];
|
||||
let dashUrl = data.video.dashUrl;
|
||||
|
||||
if (!data.video.fallbackPatch) {
|
||||
if (!Capacitor.isNativePlatform() || proxyVideos) {
|
||||
(src[0] as { src: string }).src += '?local=true';
|
||||
dashUrl += '?local=true';
|
||||
}
|
||||
}
|
||||
|
||||
player.addEventListener('dash-can-play', async () => {
|
||||
await loadPlayerPos();
|
||||
console.log(dashUrl);
|
||||
|
||||
const defaultLanguage = get(playerDefaultLanguage);
|
||||
if (defaultLanguage) {
|
||||
let trackIndex = 0;
|
||||
for (const track of player.audioTracks) {
|
||||
console.log(player.audioTracks);
|
||||
if (track.label.toLowerCase().includes(defaultLanguage)) {
|
||||
player.remoteControl.changeAudioTrack(trackIndex);
|
||||
break;
|
||||
}
|
||||
trackIndex++;
|
||||
}
|
||||
}
|
||||
await player.load(dashUrl);
|
||||
|
||||
if (get(playerAutoPlayStore)) {
|
||||
player.play();
|
||||
player.exitFullscreen();
|
||||
await loadPlayerPos();
|
||||
|
||||
const defaultLanguage = get(playerDefaultLanguage);
|
||||
if (defaultLanguage) {
|
||||
const audioLanguages = player.getAudioLanguages();
|
||||
if (audioLanguages.includes(defaultLanguage)) {
|
||||
player.selectAudioLanguage(defaultLanguage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (Capacitor.getPlatform() === 'android' && data.video.adaptiveFormats.length > 0) {
|
||||
const videoFormats = data.video.adaptiveFormats.filter((format) =>
|
||||
@@ -257,8 +243,10 @@
|
||||
|
||||
originalOrigination = await ScreenOrientation.orientation();
|
||||
|
||||
player.addEventListener('fullscreen-change', async (event: FullscreenChangeEvent) => {
|
||||
if (event.detail) {
|
||||
player.addEventListener('fullscreen-change', async () => {
|
||||
const isFullScreen = document.fullscreenElement;
|
||||
|
||||
if (isFullScreen) {
|
||||
// Ensure bar color is black while in fullscreen
|
||||
await StatusBar.setBackgroundColor({ color: '#000000' });
|
||||
await NavigationBar.setColor({
|
||||
@@ -271,7 +259,7 @@
|
||||
|
||||
if (!get(playerAndroidLockOrientation)) return;
|
||||
|
||||
if (event.detail && videoFormats[0].resolution) {
|
||||
if (isFullScreen && videoFormats[0].resolution) {
|
||||
const widthHeight = videoFormats[0].resolution.split('x');
|
||||
|
||||
if (widthHeight.length !== 2) return;
|
||||
@@ -296,17 +284,9 @@
|
||||
});
|
||||
}
|
||||
} else {
|
||||
playerIsLive = true;
|
||||
src = [
|
||||
{
|
||||
src: data.video.hlsUrl + '?local=true',
|
||||
type: 'application/x-mpegurl'
|
||||
}
|
||||
];
|
||||
await player.load(data.video.hlsUrl + '?local=true');
|
||||
}
|
||||
|
||||
player.storage = 'video-player';
|
||||
|
||||
const currentTheme = await getDynamicTheme();
|
||||
|
||||
if (data.video.fallbackPatch === 'youtubejs') {
|
||||
@@ -365,20 +345,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (toSetTime > 0) player.currentTime = toSetTime;
|
||||
if (toSetTime > 0) playerElement.currentTime = toSetTime;
|
||||
}
|
||||
|
||||
function savePlayerPos() {
|
||||
if (data.video.hlsUrl) return;
|
||||
|
||||
if (get(playerSavePlaybackPositionStore) && player && player.currentTime) {
|
||||
if (player.currentTime < player.duration - 10 && player.currentTime > 10) {
|
||||
if (get(playerSavePlaybackPositionStore) && player && playerElement.currentTime) {
|
||||
if (
|
||||
playerElement.currentTime < playerElement.duration - 10 &&
|
||||
playerElement.currentTime > 10
|
||||
) {
|
||||
try {
|
||||
localStorage.setItem(`v_${data.video.videoId}`, player.currentTime.toString());
|
||||
localStorage.setItem(`v_${data.video.videoId}`, playerElement.currentTime.toString());
|
||||
} catch {}
|
||||
|
||||
if (get(synciousStore) && get(synciousInstanceStore) && get(authStore)) {
|
||||
saveVideoProgress(data.video.videoId, player.currentTime);
|
||||
saveVideoProgress(data.video.videoId, playerElement.currentTime);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
@@ -408,8 +391,9 @@
|
||||
try {
|
||||
savePlayerPos();
|
||||
} catch (error) {}
|
||||
await player.pause();
|
||||
await playerElement.pause();
|
||||
player.destroy();
|
||||
playerElement.remove();
|
||||
playerPosSet = false;
|
||||
});
|
||||
</script>
|
||||
@@ -418,6 +402,16 @@
|
||||
<div style="margin-top: 40vh;"></div>
|
||||
{/if}
|
||||
|
||||
<div id="shaka-container" data-shaka-player-container>
|
||||
<video
|
||||
controls={false}
|
||||
autoplay={$playerAutoPlayStore}
|
||||
id="player"
|
||||
width="100%"
|
||||
poster={getBestThumbnail(data.video.videoThumbnails, 1251, 781)}
|
||||
></video>
|
||||
</div>
|
||||
|
||||
{#if !isEmbed}
|
||||
<div class="snackbar" id="snackbar-alert">
|
||||
<span class="bold" style="text-transform: capitalize;">{snackBarAlert}</span>
|
||||
|
||||
@@ -29,20 +29,6 @@ export function proxyVideoUrl(source: string): string {
|
||||
return rawSrc.toString();
|
||||
}
|
||||
|
||||
export function pullBitratePreference(): number {
|
||||
const vidstack = localStorage.getItem('video-player');
|
||||
|
||||
if (vidstack) {
|
||||
const vidstackSettings = JSON.parse(vidstack);
|
||||
if (vidstackSettings.quality && vidstackSettings.quality.bitrate) {
|
||||
return vidstackSettings.quality.bitrate;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
export function unsafeRandomItem(array: any[]): any {
|
||||
return array[Math.floor(Math.random() * array.length)];
|
||||
}
|
||||
|
||||
@@ -5,8 +5,7 @@ import { Capacitor } from '@capacitor/core';
|
||||
import type { WebPoSignalOutput } from 'bgutils-js';
|
||||
import { BG, buildURL, GOOG_API_KEY } from 'bgutils-js';
|
||||
import { get } from 'svelte/store';
|
||||
import { Innertube, UniversalCache } from 'youtubei.js';
|
||||
import { capacitorFetch } from '../android/http/capacitorFetch';
|
||||
import { Innertube, UniversalCache, YT, YTNodes } from 'youtubei.js';
|
||||
|
||||
type WebPoMinter = {
|
||||
integrityTokenBasedMinter?: BG.WebPoMinter;
|
||||
@@ -88,7 +87,7 @@ export async function patchYoutubeJs(videoId: string): Promise<VideoPlay> {
|
||||
}
|
||||
|
||||
const youtube = await Innertube.create({
|
||||
fetch: Capacitor.getPlatform() === 'android' ? capacitorFetch : fetch,
|
||||
fetch: fetch,
|
||||
generate_session_locally: true,
|
||||
cache: new UniversalCache(false),
|
||||
location: get(interfaceRegionStore)
|
||||
@@ -105,7 +104,35 @@ export async function patchYoutubeJs(videoId: string): Promise<VideoPlay> {
|
||||
sessionWebPo = poTokensCached;
|
||||
}
|
||||
|
||||
const video = await youtube.getInfo(videoId);
|
||||
const extraArgs: Record<string, any> = {
|
||||
playbackContext: {
|
||||
contentPlaybackContext: {
|
||||
vis: 0,
|
||||
splay: false,
|
||||
lactMilliseconds: '-1',
|
||||
signatureTimestamp: youtube.session.player?.sts
|
||||
}
|
||||
},
|
||||
contentCheckOk: true,
|
||||
racyCheckOk: true
|
||||
};
|
||||
|
||||
// Generate content WebPO token.
|
||||
if (integrityTokenBasedMinter) {
|
||||
extraArgs.serviceIntegrityDimensions = {
|
||||
poToken: await integrityTokenBasedMinter.mintAsWebsafeString(videoId)
|
||||
};
|
||||
}
|
||||
|
||||
const watchEndpoint = new YTNodes.NavigationEndpoint({ watchEndpoint: { videoId } });
|
||||
const rawPlayerResponse = await watchEndpoint.call(youtube.actions, extraArgs);
|
||||
const rawNextResponse = await watchEndpoint.call(youtube.actions, {
|
||||
override_endpoint: '/next',
|
||||
racyCheckOk: true,
|
||||
contentCheckOk: true
|
||||
});
|
||||
|
||||
const video = new YT.VideoInfo([rawPlayerResponse, rawNextResponse], youtube!.actions, '');
|
||||
|
||||
if (!video.primary_info || !video.secondary_info) {
|
||||
throw new Error('Unable to pull video info from youtube.js');
|
||||
@@ -115,21 +142,6 @@ export async function patchYoutubeJs(videoId: string): Promise<VideoPlay> {
|
||||
|
||||
if (!video.basic_info.is_live) {
|
||||
let manifest = await video.toDash();
|
||||
|
||||
let parser = new DOMParser();
|
||||
let xmlDoc = parser.parseFromString(manifest, 'application/xml');
|
||||
|
||||
let baseURLs = xmlDoc.getElementsByTagName('BaseURL');
|
||||
|
||||
Array.from(baseURLs).forEach((baseURL) => {
|
||||
let url = new URL(baseURL.textContent as string);
|
||||
url.searchParams.set('pot', (sessionWebPo ?? BG.PoToken.generateColdStartToken(youtube.session.context.client.visitorData ?? '')));
|
||||
baseURL.textContent = url.toString();
|
||||
});
|
||||
|
||||
const serializer = new XMLSerializer();
|
||||
manifest = serializer.serializeToString(xmlDoc);
|
||||
|
||||
dashUri = URL.createObjectURL(new Blob([manifest], { type: 'application/dash+xml;charset=utf8' }));
|
||||
}
|
||||
|
||||
@@ -199,8 +211,6 @@ export async function patchYoutubeJs(videoId: string): Promise<VideoPlay> {
|
||||
});
|
||||
}
|
||||
|
||||
console.log(video.primary_info);
|
||||
|
||||
return {
|
||||
type: 'video',
|
||||
title: video.primary_info.title?.toString() || '',
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler",
|
||||
"types": [
|
||||
"vidstack/svelte"
|
||||
"shaka-player"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
|
||||
Reference in New Issue
Block a user