More progress with porting to shaka player
This commit is contained in:
Generated
+13
@@ -25,6 +25,7 @@
|
||||
"capacitor-nodejs": "https://github.com/EdenwareApps/Capacitor-NodeJS/releases/download/v1.0.0-beta.7/capacitor6-nodejs.tgz",
|
||||
"dashjs": "^4.7.4",
|
||||
"fuse.js": "^7.0.0",
|
||||
"googlevideo": "^2.0.0",
|
||||
"he": "^1.2.0",
|
||||
"human-number": "^2.0.4",
|
||||
"iso-3166": "^4.3.0",
|
||||
@@ -7602,6 +7603,18 @@
|
||||
"integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/googlevideo": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/googlevideo/-/googlevideo-2.0.0.tgz",
|
||||
"integrity": "sha512-OVlNWZ07TPIelaEII6mH9od+Cxljl7P4AzhEYVNN5d4FhFT9L5otpcLtgvraTE9u69KfVVw+L4pVeczArcD33w==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/LuanRT"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
"capacitor-nodejs": "https://github.com/EdenwareApps/Capacitor-NodeJS/releases/download/v1.0.0-beta.7/capacitor6-nodejs.tgz",
|
||||
"dashjs": "^4.7.4",
|
||||
"fuse.js": "^7.0.0",
|
||||
"googlevideo": "^2.0.0",
|
||||
"he": "^1.2.0",
|
||||
"human-number": "^2.0.4",
|
||||
"iso-3166": "^4.3.0",
|
||||
@@ -75,4 +76,4 @@
|
||||
"terser": "^5.34.1",
|
||||
"youtubei.js": "^12.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { YT } from "youtubei.js";
|
||||
|
||||
export interface Image {
|
||||
url: string;
|
||||
width: number;
|
||||
@@ -99,6 +101,7 @@ export interface VideoPlay extends Video {
|
||||
captions: Captions[];
|
||||
storyboards?: StoryBoard[];
|
||||
fallbackPatch?: 'youtubejs' | 'piped';
|
||||
youtubeJsPatchInfo?: YT.VideoInfo;
|
||||
}
|
||||
|
||||
export interface StoryBoard {
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
import { ScreenOrientation, type ScreenOrientationResult } from '@capacitor/screen-orientation';
|
||||
import { StatusBar, Style } from '@capacitor/status-bar';
|
||||
import { NavigationBar } from '@hugotomazi/capacitor-navigation-bar';
|
||||
import { error, type Page } from '@sveltejs/kit';
|
||||
import { type Page } from '@sveltejs/kit';
|
||||
import GoogleVideo, { Protos } from 'googlevideo';
|
||||
import 'shaka-player/dist/controls.css';
|
||||
import shaka from 'shaka-player/dist/shaka-player.ui';
|
||||
import { SponsorBlock, type Category, type Segment } from 'sponsorblock-api';
|
||||
@@ -73,7 +74,7 @@
|
||||
onMount(async () => {
|
||||
shaka.polyfill.installAll();
|
||||
if (!shaka.Player.isBrowserSupported()) {
|
||||
error(400, 'Shaka not supported on your browser');
|
||||
return;
|
||||
}
|
||||
|
||||
player = new shaka.Player();
|
||||
@@ -81,6 +82,142 @@
|
||||
|
||||
await player.attach(playerElement);
|
||||
|
||||
if (data.video.youtubeJsPatchInfo) {
|
||||
player.configure({
|
||||
streaming: {
|
||||
bufferingGoal:
|
||||
(data.video.youtubeJsPatchInfo.page[0].player_config?.media_common_config
|
||||
.dynamic_readahead_config.max_read_ahead_media_time_ms || 0) / 1000,
|
||||
rebufferingGoal:
|
||||
(data.video.youtubeJsPatchInfo.page[0].player_config?.media_common_config
|
||||
.dynamic_readahead_config.read_ahead_growth_rate_ms || 0) / 1000,
|
||||
bufferBehind: 300,
|
||||
autoLowLatencyMode: true
|
||||
},
|
||||
abr: {
|
||||
enabled: true,
|
||||
restrictions: {
|
||||
maxBandwidth: Number(
|
||||
data.video.youtubeJsPatchInfo.page[0].player_config?.stream_selection_config
|
||||
.max_bitrate
|
||||
)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
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).toString());
|
||||
}
|
||||
}
|
||||
|
||||
request.method = 'POST';
|
||||
request.body = new Uint8Array([120, 0]);
|
||||
}
|
||||
|
||||
if (Capacitor.getPlatform() === 'android' && data.video.fallbackPatch === 'youtubejs') {
|
||||
request.uris[0] = localProxy + url.toString();
|
||||
} else {
|
||||
request.uris[0] = url.toString();
|
||||
}
|
||||
});
|
||||
|
||||
const RequestType = shaka.net.NetworkingEngine.RequestType;
|
||||
|
||||
networkingEngine.registerResponseFilter(async (type, response) => {
|
||||
let mediaData = new Uint8Array(0);
|
||||
|
||||
const handleRedirect = async (redirectData: Protos.SabrRedirect) => {
|
||||
const redirectRequest = shaka.net.NetworkingEngine.makeRequest(
|
||||
[redirectData.url!],
|
||||
player!.getConfiguration().streaming.retryParameters
|
||||
);
|
||||
const requestOperation = player!.getNetworkingEngine()!.request(type, redirectRequest);
|
||||
const redirectResponse = await requestOperation.promise;
|
||||
|
||||
response.data = redirectResponse.data;
|
||||
response.headers = redirectResponse.headers;
|
||||
response.uri = redirectResponse.uri;
|
||||
};
|
||||
|
||||
const handleMediaData = async (data: Uint8Array) => {
|
||||
const combinedLength = mediaData.length + data.length;
|
||||
const tempMediaData = new Uint8Array(combinedLength);
|
||||
|
||||
tempMediaData.set(mediaData);
|
||||
tempMediaData.set(data, mediaData.length);
|
||||
|
||||
mediaData = tempMediaData;
|
||||
};
|
||||
|
||||
if (type == RequestType.SEGMENT) {
|
||||
const googUmp = new GoogleVideo.UMP(
|
||||
new GoogleVideo.ChunkedDataBuffer([new Uint8Array(response.data as ArrayBuffer)])
|
||||
);
|
||||
|
||||
let redirect: Protos.SabrRedirect | undefined;
|
||||
|
||||
googUmp.parse((part) => {
|
||||
try {
|
||||
const data = part.data.chunks[0];
|
||||
switch (part.type) {
|
||||
case 20: {
|
||||
const mediaHeader = Protos.MediaHeader.decode(data);
|
||||
console.info('[MediaHeader]:', mediaHeader);
|
||||
break;
|
||||
}
|
||||
case 21: {
|
||||
handleMediaData(part.data.split(1).remainingBuffer.chunks[0]);
|
||||
break;
|
||||
}
|
||||
case 43: {
|
||||
redirect = Protos.SabrRedirect.decode(data);
|
||||
console.info('[SABRRedirect]:', redirect);
|
||||
break;
|
||||
}
|
||||
case 58: {
|
||||
const streamProtectionStatus = Protos.StreamProtectionStatus.decode(data);
|
||||
switch (streamProtectionStatus.status) {
|
||||
case 1:
|
||||
console.info('[StreamProtectionStatus]: Ok');
|
||||
break;
|
||||
case 2:
|
||||
console.error('[StreamProtectionStatus]: Attestation pending');
|
||||
break;
|
||||
case 3:
|
||||
console.error('[StreamProtectionStatus]: Attestation required');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('An error occurred while processing the part:', error);
|
||||
}
|
||||
});
|
||||
|
||||
if (redirect) return handleRedirect(redirect);
|
||||
|
||||
if (mediaData.length) response.data = mediaData;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const shakaUi = new shaka.ui.Overlay(
|
||||
player,
|
||||
document.getElementById('shaka-container') as HTMLElement,
|
||||
@@ -88,56 +225,26 @@
|
||||
);
|
||||
|
||||
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;
|
||||
controlPanelElements: [
|
||||
'play_pause',
|
||||
'spacer',
|
||||
'volume',
|
||||
'chapter',
|
||||
'time_and_duration',
|
||||
'overflow_menu'
|
||||
],
|
||||
overflowMenuButtons: ['cast', 'airplay', 'captions', 'quality', 'loop', 'language']
|
||||
});
|
||||
|
||||
if (!data.video.hlsUrl) {
|
||||
let dashUrl = data.video.dashUrl;
|
||||
|
||||
if (!data.video.fallbackPatch && (!Capacitor.isNativePlatform() || proxyVideos)) {
|
||||
dashUrl += '?local=true';
|
||||
}
|
||||
|
||||
await player.load(dashUrl);
|
||||
|
||||
if (data.video.captions) {
|
||||
data.video.captions.forEach(async (caption) => {
|
||||
player.addTextTrackAsync(
|
||||
@@ -214,18 +321,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
let dashUrl = data.video.dashUrl;
|
||||
|
||||
if (!data.video.fallbackPatch) {
|
||||
if (!Capacitor.isNativePlatform() || proxyVideos) {
|
||||
dashUrl += '?local=true';
|
||||
}
|
||||
}
|
||||
|
||||
console.log(dashUrl);
|
||||
|
||||
await player.load(dashUrl);
|
||||
|
||||
await loadPlayerPos();
|
||||
|
||||
const defaultLanguage = get(playerDefaultLanguage);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { numberWithCommas } from '$lib/time';
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
import type { WebPoSignalOutput } from 'bgutils-js';
|
||||
import { BG, buildURL, GOOG_API_KEY } from 'bgutils-js';
|
||||
import { Buffer } from 'buffer';
|
||||
import { get } from 'svelte/store';
|
||||
import { Innertube, UniversalCache, YT, YTNodes } from 'youtubei.js';
|
||||
|
||||
@@ -141,8 +142,8 @@ export async function patchYoutubeJs(videoId: string): Promise<VideoPlay> {
|
||||
let dashUri: string = '';
|
||||
|
||||
if (!video.basic_info.is_live) {
|
||||
let manifest = await video.toDash();
|
||||
dashUri = URL.createObjectURL(new Blob([manifest], { type: 'application/dash+xml;charset=utf8' }));
|
||||
const manifest = await video.toDash();
|
||||
dashUri = `data:application/dash+xml;charset=utf-8;base64,${Buffer.from(manifest).toString('base64')}`;
|
||||
}
|
||||
|
||||
const descString = video.secondary_info.description?.toString() || '';
|
||||
@@ -250,6 +251,7 @@ export async function patchYoutubeJs(videoId: string): Promise<VideoPlay> {
|
||||
subCountText: '',
|
||||
keywords: video.basic_info.keywords || [],
|
||||
allowedRegions: [],
|
||||
fallbackPatch: 'youtubejs'
|
||||
fallbackPatch: 'youtubejs',
|
||||
youtubeJsPatchInfo: video
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,48 +2,6 @@
|
||||
padding: 0.1em 0.4em;
|
||||
}
|
||||
|
||||
.vds-chapter-radio-label {
|
||||
word-wrap: break-word !important;
|
||||
overflow-wrap: break-word !important;
|
||||
white-space: normal !important;
|
||||
}
|
||||
|
||||
.vds-chapter-radio-content {
|
||||
max-width: 200px !important;
|
||||
}
|
||||
|
||||
.vds-kb-action {
|
||||
position: absolute !important;
|
||||
top: 50% !important;
|
||||
left: 50% !important;
|
||||
}
|
||||
|
||||
.vds-chapter-radio {
|
||||
align-items: start !important;
|
||||
}
|
||||
|
||||
.vds-time-divider {
|
||||
font-size: 0 !important;
|
||||
margin-right: 5px !important;
|
||||
}
|
||||
|
||||
.sponsorskip-bar {
|
||||
position: absolute;
|
||||
margin-top: 2px;
|
||||
height: 5px;
|
||||
top: -1;
|
||||
border-radius: 1em;
|
||||
}
|
||||
|
||||
.vds-time-slider {
|
||||
position: relative !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.vds-poster :where(img) {
|
||||
object-fit: cover !important;
|
||||
}
|
||||
|
||||
.infinite-status-prompt {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user