Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

7 #183

Closed
wants to merge 78 commits into from
Closed

7 #183

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
df5806b
testing netlify functions 1
n-ce May 1, 2024
cfb0b04
add link tag
n-ce May 3, 2024
3b84d5d
oembed
n-ce May 3, 2024
bbbd21f
context-site-url
n-ce May 3, 2024
41df38c
set noembed
n-ce May 3, 2024
94f4bde
update noembed api
n-ce May 3, 2024
7a5ab47
context params
n-ce May 3, 2024
0aba04a
use req url
n-ce May 3, 2024
7311465
return json
n-ce May 3, 2024
ea62834
test opengraph edge function
n-ce May 4, 2024
115f898
rename edge function to query param
n-ce May 4, 2024
0caea6d
remove serverless function
n-ce May 4, 2024
2eb3d7a
fix silly mistake
n-ce May 4, 2024
54f8ca3
fix silly mistake
n-ce May 4, 2024
3019481
enable test response for body change
n-ce May 4, 2024
cb7b450
move to correct directory
n-ce May 4, 2024
6e03996
rename to edge-function directory
n-ce May 4, 2024
7bb089f
test head change response
n-ce May 4, 2024
ee65b20
test function name param overlap
n-ce May 4, 2024
83ec1b0
use injection point to dynamically add og tags
n-ce May 4, 2024
3ee290d
return piped data response
n-ce May 4, 2024
b77cf6d
improve code
n-ce May 4, 2024
90ed6a4
fix lint include keywords reassignment
n-ce May 4, 2024
8536302
use replaceAll for thumbnail
n-ce May 4, 2024
25d1eb8
change lib to es2021
n-ce May 4, 2024
4ea639e
fix lint
n-ce May 4, 2024
41e99e0
test fix
n-ce May 4, 2024
1d145dc
test fix
n-ce May 4, 2024
34d0ca8
test fix
n-ce May 4, 2024
75cedfe
test fix
n-ce May 4, 2024
6754bdc
test fix
n-ce May 4, 2024
cf18ba4
test fix
n-ce May 4, 2024
bccda1f
initial solid-js component (1/4)
n-ce May 5, 2024
2295e9c
migrarion almost finished except for rendering library collections, m…
n-ce May 6, 2024
1dd57b2
cleanup
n-ce May 6, 2024
1cfd76f
6.9.4
Tyrritt May 1, 2024
3ac3dc8
initial alpha release for v7, lots of broken stuff
n-ce May 13, 2024
59dbd18
retry build
n-ce May 13, 2024
6bece05
retry build
n-ce May 13, 2024
8f9dae0
layout updates
n-ce May 13, 2024
caf181b
layout updates
n-ce May 13, 2024
cdd2e2e
safari fix attempt
n-ce May 14, 2024
50d7243
initial HLS support
n-ce May 14, 2024
a8d1077
add draggable to streamitem
n-ce May 17, 2024
2bf833e
setup upcoming queries directly with netlify function
n-ce May 17, 2024
95db83e
fix netlify build
n-ce May 17, 2024
c3843ed
fix upcoming query url
n-ce May 17, 2024
d718d1c
combine piped instances and invidious instances to reduce load for up…
n-ce May 17, 2024
5296533
fix invidious instance insertion
n-ce May 17, 2024
7ce7f4a
fix invidious instance insertion
n-ce May 17, 2024
6a2fe1e
fix invidious instance data field
n-ce May 17, 2024
75c51a8
parallelize data fetching
n-ce May 17, 2024
a3e005c
parallelize using promise all
n-ce May 17, 2024
7915519
delegate navigation events
n-ce May 17, 2024
196eba3
do not mount img element when thumbnails are off
n-ce May 17, 2024
3d113df
show loading screen for upcoming queue query & fix update prompt layout
n-ce May 17, 2024
4e7ec6f
transfer featured playlists to library
n-ce May 17, 2024
1afaad8
fix ivApi music streams playback
n-ce May 18, 2024
3733233
fix codefactor
n-ce May 18, 2024
eb96645
fix codefactor
n-ce May 18, 2024
f4a9368
Merge branch 'main' into oembed
n-ce May 18, 2024
c2811e1
fix ivPlyr music playback
n-ce May 19, 2024
1758bb1
initial support for miniplayer
n-ce May 19, 2024
6f838f8
add miniPlayer functionality
n-ce May 20, 2024
d3fa724
sync local
n-ce May 24, 2024
985d828
[CodeFactor] Apply fixes
code-factor May 24, 2024
ae1ebf1
fix netlify build
n-ce May 25, 2024
1e5c5a8
testing opengraph audio tag
n-ce May 26, 2024
1a377b7
fix build
n-ce May 26, 2024
4624c89
forgot to add closing tag
n-ce May 26, 2024
43a8661
add more og audio tags
n-ce May 26, 2024
691df3a
test more tags
n-ce May 26, 2024
85051c1
test more tags
n-ce May 26, 2024
6381e55
switch to invidious for opengraph api
n-ce May 26, 2024
3aea09a
upgrade to unified instances v2 api
n-ce May 27, 2024
868f8ab
fix opengraph response
n-ce May 27, 2024
6f34840
fix opengraph response
n-ce May 27, 2024
ef45037
support for channel and playlist subscriptions
n-ce May 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
491 changes: 262 additions & 229 deletions index.html

Large diffs are not rendered by default.

46 changes: 46 additions & 0 deletions netlify/edge-functions/opengraph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Context, Config } from '@netlify/edge-functions';

export default async (request: Request, context: Context) => {

const req = new URL(request.url);

if (!req.searchParams.has('s')) return;

const id = req.searchParams.get('s');

if (id?.length !== 11) return;

const response = await context.next();
const page = await response.text();
const instance = 'https://invidious.fdn.fr';
const data = await fetch(instance + '/api/v1/videos/' + id).then(res => res.json());

// select the lowest bitrate aac stream i.e itag 139
let audioSrc = data.adaptiveFormats.find((v: { itag: number }) => v.itag == 139).url;

// Conditionally only proxy music streams
if (data.genre === 'Music')
audioSrc = audioSrc.replace(new URL(audioSrc).origin, instance);

const newPage = page
.replace('48-160kbps Opus YouTube Audio Streaming Web App.', data.author)
.replace('"ytify"', `"${data.title}"`)
.replace(<string>context.site.url, `${context.site.url}?s=${id}`)
.replaceAll('/ytify_thumbnail_min.webp', data.videoThumbnails.find((v: { quality: string }) => v.quality === 'medium').url)
// for audio embedding
.replace('<!-- a4 -->',
`<meta property="og:audio" content="${audioSrc}">
<meta property="og:audio:secure_url" content="${audioSrc}">
<meta property="og:video" content="${audioSrc}">
<meta property="og:audio:type" content="audio/aac">
<meta property="music.duration" content="${data.lengthSeconds}">`
)
.replace('"website"', '"music.song"');


return new Response(newPage, response);
};

export const config: Config = {
path: '/*',
};
75 changes: 75 additions & 0 deletions netlify/edge-functions/upcoming.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// handles upcoming query requests to restore queue from any where

import { Config } from '@netlify/edge-functions';

function convertSStoHHMMSS(seconds: number) {
if (seconds < 0) return '';
const hh = Math.floor(seconds / 3600);
seconds %= 3600;
const mm = Math.floor(seconds / 60);
const ss = Math.floor(seconds % 60);
let mmStr = String(mm);
let ssStr = String(ss);
if (mm < 10) mmStr = '0' + mmStr;
if (ss < 10) ssStr = '0' + ssStr;
return (hh > 0 ?
hh + ':' : '') + `${mmStr}:${ssStr}`;
}

const instanceArray = await fetch('https://piped-instances.kavin.rocks')
.then(res => res.json())
.then(data => data.map((i: { api_url: string }) => i.api_url + '/streams/'))
.catch(() => ['https://pipedapi.kavin.rocks/streams/']);

await fetch('https://api.invidious.io/instances.json')
.then(res => res.json())
.then(data => {
for (const i of data)
if (i[1].cors && i[1].api)
instanceArray.push(i[1].uri + '/api/v1/videos/')

})
.catch(() => ['https://invidious.fdn.fr/api/v1/videos/']);


const getIndex = () => Math.floor(Math.random() * instanceArray.length);


export default async (request: Request) => {

const uid = new URL(request.url).searchParams.get('id');

if (!uid) return;

const array = [];
for (let i = 0; i < uid.length; i += 11)
array.push(uid.slice(i, i + 11));

const getData = async (
id: string,
api: string = instanceArray[getIndex()]
): Promise<Record<'id' | 'title' | 'author' | 'authorId' | 'duration' | 'duration' | 'thumbnailUrl' | 'source', string>> => await fetch(api + id)
.then(res => res.json())
.then(json => ({
'id': id,
'title': json.title,
'author': json.uploader || json.author,
'authorId': json.authorUrl || json.uploaderUrl.slice(9),
'duration': convertSStoHHMMSS(json.duration || json.lengthSeconds),
'thumbnailUrl': json.thumbnailUrl || json.videoThumbnails[4].url,
'source': api + id
}))
.catch(() => getData(id))


const promises = array.map(async (id) => await getData(id));
const response = await Promise.all(promises);

return new Response(JSON.stringify(response), {
headers: { 'content-type': 'application/json' },
});
};

export const config: Config = {
path: '/upcoming',
};
11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ytify",
"version": "6.9.2",
"version": "7.0.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand All @@ -9,15 +9,18 @@
"update": "npx npm-check-updates -u"
},
"dependencies": {
"hls.js": "^1.5.8",
"imsc": "^1.1.5",
"lit": "^3.1.3"
"solid-js": "^1.8.17"
},
"devDependencies": {
"@netlify/edge-functions": "^2.8.1",
"autoprefixer": "^10.4.19",
"eruda": "^3.0.1",
"typescript": "^5.4.5",
"vite": "^5.2.10",
"vite-plugin-pwa": "^0.19.8"
"vite": "^5.2.11",
"vite-plugin-pwa": "^0.20.0",
"vite-plugin-solid": "^2.10.2"
},
"browserslist": [
"defaults"
Expand Down
Binary file modified public/remixicon.woff2
Binary file not shown.
62 changes: 62 additions & 0 deletions src/components/ListItem.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
.listItem {
width: 48%;
height: auto;
background-color: var(--onBg);
padding: 1dvmin;
margin: 1%;
border: var(--border);
border-radius: calc(var(--roundness) + 0.75vmin);
transition: all 0.3s ease-out;
display: inline-block;

@media(orientation:landscape) {
width: 23%;
}

* {
pointer-events: none;
transition: all 0.4s;
}

&:hover {
transform: scale(0.95);
}

&.ravel {
* {
opacity: 0;
}
}


img {
height: auto;
width: 100%;
border-radius: var(--roundness);
}

div {
display: flex;
flex-direction: column;
}

p.title {
font-size: medium;
height: 2rem;
line-height: 1rem;
overflow: hidden;
}

p.uData {
font-size: x-small;
line-height: 0.8rem;
overflow: hidden;
}

p.stats {
font-size: small;
height: 1rem;
line-height: 1rem;
}

}
56 changes: 56 additions & 0 deletions src/components/ListItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { hostResolver } from '../lib/utils';
import './ListItem.css';
import { Show, createSignal } from 'solid-js';

// workaround "cannot access 'getSaved' before initialization"
const s = localStorage.getItem('imgLoad');
const showImage = (s === 'off') ? undefined : s ? 'lazy' : 'eager';

export default function ListItem(
title: string,
stats: string,
thumbnail: string,
uploader_data: string,
url: string,
) {
const [getThumbnail, setThumbnail] = createSignal(thumbnail);

const handleError = () =>
setThumbnail(
getThumbnail().includes('rj')
? getThumbnail().replace('rj', 'rw')
: '/logo192.png'
);

function handleLoad(e: Event) {
const img = e.target as HTMLImageElement;
img.parentElement!.classList.remove('ravel');

if (img.naturalHeight === 90)
setThumbnail(getThumbnail().replace('_webp', '').replace('webp', 'jpg'));
}


return (
<a
class={'listItem ' + (showImage ? 'ravel' : '')}
href={hostResolver(url)}
data-url={url}
data-thumbnail={thumbnail}
>
<Show when={showImage}>
<img
loading={showImage as 'lazy' | 'eager' | undefined}
src={getThumbnail()}
onError={handleError}
onLoad={handleLoad}
/>
</Show>
<div>
<p class="title">{title}</p>
<p class="uData">{uploader_data}</p>
<p class="stats">{stats}</p>
</div>
</a>
);
}
67 changes: 67 additions & 0 deletions src/components/StreamItem.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
.streamItem {
width: 100%;
user-select: none;
background-color: var(--onBg);
padding: 1dvmin;
margin-bottom: 2dvmin;
border: var(--border);
border-radius: calc(var(--roundness) + 0.75vmin);
transition: transform 0.2s ease-out;
display: flex;
align-items: center;

* {
pointer-events: none;
transition: opacity 0.4s;
}

&:hover {
transform: scale(0.95);
}

&.ravel {
* {
opacity: 0;
}
}


span {
position: relative;
z-index: 0;
width: 50dvmin;
margin-right: 1vmin;


img {
width: 100%;
border-radius: var(--roundness);
}

.duration {
position: absolute;
padding: 0 1vmin;
bottom: 1.1vmin;
right: 1.2vmin;
background-color: #000a;
color: #fffc;
font-family: monospace;
border-radius: calc(var(--roundness)*1.1);
}
}

div {
width: 100%;

.avu {
font-size: small;
opacity: 0.8;

@media(orientation:landscape) {
display: flex;
justify-content: space-between;
}
}
}

}