mirror of
https://codeberg.org/Hyperpipe/Hyperpipe
synced 2025-06-27 20:58:01 +02:00
Switched to Composition API
This commit is contained in:
parent
75b0a9b319
commit
8ee03fa623
11 changed files with 705 additions and 647 deletions
|
@ -45,6 +45,8 @@ npm run build
|
||||||
|
|
||||||
Please refer to LICENSE.
|
Please refer to LICENSE.
|
||||||
|
|
||||||
|
You can reach out to me on <TODO>
|
||||||
|
|
||||||
### Dependencies and Mentions
|
### Dependencies and Mentions
|
||||||
|
|
||||||
- VueJS -> [MIT][vue]
|
- VueJS -> [MIT][vue]
|
||||||
|
|
729
src/App.vue
729
src/App.vue
|
@ -1,5 +1,5 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue';
|
import { ref, reactive, onMounted } from 'vue';
|
||||||
|
|
||||||
import NavBar from './components/NavBar.vue';
|
import NavBar from './components/NavBar.vue';
|
||||||
import StatusBar from './components/StatusBar.vue';
|
import StatusBar from './components/StatusBar.vue';
|
||||||
|
@ -8,7 +8,310 @@ import Search from './components/Search.vue';
|
||||||
import Playlists from './components/Playlists.vue';
|
import Playlists from './components/Playlists.vue';
|
||||||
import Artist from './components/Artist.vue';
|
import Artist from './components/Artist.vue';
|
||||||
|
|
||||||
let search = ref('');
|
import { getJson, getJsonPiped } from './scripts/fetch.js'
|
||||||
|
|
||||||
|
const data = reactive({
|
||||||
|
artUrl: '',
|
||||||
|
cover: '',
|
||||||
|
audioSrc: '',
|
||||||
|
url: '',
|
||||||
|
urls: [],
|
||||||
|
songItems: null,
|
||||||
|
hls: null,
|
||||||
|
items: {},
|
||||||
|
title: '',
|
||||||
|
artist: '',
|
||||||
|
state: 'play',
|
||||||
|
duration: 0,
|
||||||
|
time: 0,
|
||||||
|
showplaylist: false,
|
||||||
|
loop: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const artist = reactive({
|
||||||
|
playlistId: null,
|
||||||
|
title: null,
|
||||||
|
description: null,
|
||||||
|
subscriberCount: 0,
|
||||||
|
thumbnails: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const search = ref('');
|
||||||
|
|
||||||
|
const audio = ref(null);
|
||||||
|
|
||||||
|
function parseUrl() {
|
||||||
|
const loc = location.href.split('/');
|
||||||
|
|
||||||
|
console.log(loc);
|
||||||
|
|
||||||
|
switch (loc[3].replace(location.search, '')) {
|
||||||
|
case '':
|
||||||
|
case 'search':
|
||||||
|
search.value = loc[4];
|
||||||
|
console.log(search.value);
|
||||||
|
break;
|
||||||
|
case 'watch':
|
||||||
|
getSong(loc[3]);
|
||||||
|
console.log(loc[3]);
|
||||||
|
break;
|
||||||
|
case 'playlist':
|
||||||
|
getAlbum(loc[3]);
|
||||||
|
console.log(loc[3]);
|
||||||
|
break;
|
||||||
|
case 'channel':
|
||||||
|
getArtist(loc[4]);
|
||||||
|
console.log(loc[4]);
|
||||||
|
default:
|
||||||
|
console.log(loc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Toggle(e) {
|
||||||
|
console.log(e, data[e]);
|
||||||
|
|
||||||
|
if (data[e]) {
|
||||||
|
data[e] = false;
|
||||||
|
} else {
|
||||||
|
data[e] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Update(e) {
|
||||||
|
search.value = e;
|
||||||
|
console.log('update');
|
||||||
|
}
|
||||||
|
|
||||||
|
function timeUpdate(t) {
|
||||||
|
data.time = Math.floor( (t / data.duration) * 100 );
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTime(t) {
|
||||||
|
audio.value.currentTime = (t / 100) * data.duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSong(s) {
|
||||||
|
data.urls = [s];
|
||||||
|
playNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSong(s) {
|
||||||
|
data.urls.push(s);
|
||||||
|
|
||||||
|
const index = data.urls.map((s) => s.url).indexOf(data.url);
|
||||||
|
|
||||||
|
if (
|
||||||
|
(index == data.urls.length - 1 && data.time > 98) ||
|
||||||
|
data.urls.length == 1
|
||||||
|
) {
|
||||||
|
console.log(true);
|
||||||
|
playNext();
|
||||||
|
} else {
|
||||||
|
console.log(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(s, data.urls);
|
||||||
|
}
|
||||||
|
|
||||||
|
function playThis(t) {
|
||||||
|
const i = data.urls.indexOf(t);
|
||||||
|
getSong(data.urls[i].url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function playList(a) {
|
||||||
|
data.urls = a;
|
||||||
|
getSong(data.urls[0].url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function playNext(u) {
|
||||||
|
if (data.hls) {
|
||||||
|
data.hls.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
const i = data.urls.map((s) => s.url).indexOf(data.url),
|
||||||
|
next = data.urls[i + 1];
|
||||||
|
|
||||||
|
console.log('Index: ' + i);
|
||||||
|
console.log(data.url, data.urls, next);
|
||||||
|
|
||||||
|
if (data.urls.length > i && data.urls.length != 0 && next) {
|
||||||
|
getSong(next.url);
|
||||||
|
} else if (data.loop) {
|
||||||
|
data.url = data.urls[0].url;
|
||||||
|
getSong(data.urls[0].url);
|
||||||
|
} else {
|
||||||
|
data.urls = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSong(e) {
|
||||||
|
console.log(e);
|
||||||
|
|
||||||
|
const hash = new URLSearchParams(e.substring(e.indexOf('?'))).get('v'),
|
||||||
|
json = await getJsonPiped('/streams/' + hash);
|
||||||
|
|
||||||
|
console.log(json);
|
||||||
|
|
||||||
|
Stream({
|
||||||
|
art: json.thumbnailUrl,
|
||||||
|
artist: json.uploader,
|
||||||
|
time: json.duration,
|
||||||
|
hls: json.hls,
|
||||||
|
stream: json.audioStreams[0].url,
|
||||||
|
title: json.title,
|
||||||
|
url: e,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAlbum(e) {
|
||||||
|
console.log('Album: ', e);
|
||||||
|
|
||||||
|
const hash = new URLSearchParams(e.substring(e.indexOf('?'))).get('list'),
|
||||||
|
json = await getJsonPiped('/playlists/' + hash);
|
||||||
|
|
||||||
|
console.log(json, json.relatedStreams);
|
||||||
|
|
||||||
|
data.songItems = {
|
||||||
|
items: json.relatedStreams,
|
||||||
|
title: json.name,
|
||||||
|
};
|
||||||
|
|
||||||
|
history.pushState({}, '', e);
|
||||||
|
|
||||||
|
for (let i in artist) {
|
||||||
|
artist[i] = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getArtist(e) {
|
||||||
|
console.log(e);
|
||||||
|
|
||||||
|
const json = await getJson(
|
||||||
|
'https://hyperpipeapi.onrender.com/channel/' + e,
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(json);
|
||||||
|
|
||||||
|
data.items = json.items;
|
||||||
|
data.items.notes = json.playlistId;
|
||||||
|
json.items = null;
|
||||||
|
|
||||||
|
for(let i in json) {
|
||||||
|
artist[i] = json[i];
|
||||||
|
};
|
||||||
|
|
||||||
|
history.pushState({}, '', '/channel/' + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setVolume(vol) {
|
||||||
|
audio.value.volume = vol;
|
||||||
|
}
|
||||||
|
|
||||||
|
function playPause() {
|
||||||
|
if (audio.value.paused) {
|
||||||
|
audio.value.play();
|
||||||
|
data.state = 'pause';
|
||||||
|
} else {
|
||||||
|
audio.value.pause();
|
||||||
|
data.state = 'play';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Stream(res) {
|
||||||
|
console.log(res);
|
||||||
|
|
||||||
|
data.art = res.art;
|
||||||
|
data.cover = `--art: url(${res.art});`;
|
||||||
|
data.nowtitle = res.title;
|
||||||
|
data.nowartist = res.artist.split(' - ')[0];
|
||||||
|
data.duration = res.time;
|
||||||
|
data.url = res.url;
|
||||||
|
|
||||||
|
if (!!Hls && Hls.isSupported()) {
|
||||||
|
data.hls = new Hls();
|
||||||
|
|
||||||
|
console.log(data.hls.levels);
|
||||||
|
data.hls.loadSource(res.hls);
|
||||||
|
data.hls.attachMedia(audio.value);
|
||||||
|
} else {
|
||||||
|
data.audioSrc = res.stream;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function audioCanPlay() {
|
||||||
|
lazyLoad();
|
||||||
|
audio.value.play();
|
||||||
|
data.state = 'pause';
|
||||||
|
|
||||||
|
if (location.pathname != '/playlist') {
|
||||||
|
history.pushState({}, '', data.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.title = `Playing: ${data.nowtitle} by ${data.nowartist}`;
|
||||||
|
|
||||||
|
setMetadata(data.art);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMetadata(art) {
|
||||||
|
if (navigator.mediaSession) {
|
||||||
|
navigator.mediaSession.metadata = new MediaMetadata({
|
||||||
|
title: data.nowtitle,
|
||||||
|
artist: data.nowartist,
|
||||||
|
artwork: [
|
||||||
|
{
|
||||||
|
src: art,
|
||||||
|
type: 'image/png',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function lazyLoad() {
|
||||||
|
let lazyElems;
|
||||||
|
|
||||||
|
if ('IntersectionObserver' in window) {
|
||||||
|
lazyElems = document.querySelectorAll('.bg-img:not(.lazy)');
|
||||||
|
|
||||||
|
let imgObs = new IntersectionObserver((elems, obs) => {
|
||||||
|
elems.forEach((elem) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (elem.isIntersecting) {
|
||||||
|
let ele = elem.target;
|
||||||
|
|
||||||
|
ele.classList.add('lazy');
|
||||||
|
imgObs.unobserve(ele);
|
||||||
|
}
|
||||||
|
}, 20);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
lazyElems.forEach((img) => {
|
||||||
|
imgObs.observe(img);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('Failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
lazyLoad();
|
||||||
|
|
||||||
|
document.addEventListener('scroll', lazyLoad);
|
||||||
|
document.addEventListener('resize', lazyLoad);
|
||||||
|
document.addEventListener('orientationChange', lazyLoad);
|
||||||
|
|
||||||
|
window.addEventListener('popstate', parseUrl);
|
||||||
|
window.onbeforeunload = () => {
|
||||||
|
return 'Are you Sure?';
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(audio.value)
|
||||||
|
|
||||||
|
parseUrl();
|
||||||
|
|
||||||
|
console.log('Mounted <App>!');
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -31,355 +334,52 @@ let search = ref('');
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<header v-if="!artist.title">
|
<header v-if="!artist.title">
|
||||||
<div v-if="cover" class="art bg-img" :style="cover"></div>
|
<div v-if="data.cover" class="art bg-img" :style="data.cover"></div>
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<NowPlaying :title="nowtitle" :artist="nowartist" />
|
<NowPlaying :title="data.nowtitle" :artist="data.nowartist" />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="placeholder">
|
<main class="placeholder">
|
||||||
<Search
|
<Search
|
||||||
@get-song="setSong"
|
|
||||||
@get-album="getAlbum"
|
@get-album="getAlbum"
|
||||||
@get-artist="getArtist"
|
@get-artist="getArtist"
|
||||||
@lazy="lazyLoad"
|
@lazy="lazyLoad"
|
||||||
@play-urls="playList"
|
@play-urls="playList"
|
||||||
@add-song="addSong"
|
@add-song="addSong"
|
||||||
:items="items"
|
:items="data.items"
|
||||||
:songItems="songItems"
|
:songItems="data.songItems"
|
||||||
:search="search" />
|
:search="search" />
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<Playlists
|
<Playlists
|
||||||
@playthis="playThis"
|
@playthis="playThis"
|
||||||
:url="url"
|
:url="data.url"
|
||||||
:urls="urls"
|
:urls="data.urls"
|
||||||
:show="showplaylist" />
|
:show="data.showplaylist" />
|
||||||
|
|
||||||
<StatusBar
|
<StatusBar
|
||||||
@play="playPause"
|
@play="playPause"
|
||||||
@vol="setVolume"
|
@vol="setVolume"
|
||||||
@list="Toggle"
|
@list="Toggle"
|
||||||
@loop="Toggle"
|
@loop="Toggle"
|
||||||
:state="state"
|
@change-time="setTime"
|
||||||
:time="time"
|
:state="data.state"
|
||||||
:show="showplaylist"
|
:time="data.time"
|
||||||
:loop="loop" />
|
:show="data.showplaylist"
|
||||||
|
:loop="data.loop" />
|
||||||
|
|
||||||
<audio
|
<audio
|
||||||
id="audio"
|
id="audio"
|
||||||
ref="audio"
|
ref="audio"
|
||||||
:src="audioSrc"
|
:src="data.audioSrc"
|
||||||
@canplay="audioCanPlay"
|
@canplay="audioCanPlay"
|
||||||
@timeupdate="timeUpdate($event.target.currentTime)"
|
@timeupdate="timeUpdate($event.target.currentTime)"
|
||||||
@ended="playNext"
|
@ended="playNext"
|
||||||
autoplay></audio>
|
autoplay></audio>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
artUrl: '',
|
|
||||||
cover: '',
|
|
||||||
nowtitle: '',
|
|
||||||
nowartist: '',
|
|
||||||
state: 'play',
|
|
||||||
audioSrc: '',
|
|
||||||
duration: 0,
|
|
||||||
time: 0,
|
|
||||||
url: '',
|
|
||||||
urls: [],
|
|
||||||
songItems: null,
|
|
||||||
showplaylist: false,
|
|
||||||
loop: false,
|
|
||||||
hls: null,
|
|
||||||
artist: {
|
|
||||||
playlistId: null,
|
|
||||||
title: null,
|
|
||||||
description: null,
|
|
||||||
subscriberCount: 0,
|
|
||||||
thumbnails: [],
|
|
||||||
},
|
|
||||||
items: {},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.lazyLoad();
|
|
||||||
|
|
||||||
document.addEventListener('scroll', this.lazyLoad);
|
|
||||||
document.addEventListener('resize', this.lazyLoad);
|
|
||||||
document.addEventListener('orientationChange', this.lazyLoad);
|
|
||||||
|
|
||||||
window.addEventListener('popstate', this.parseUrl);
|
|
||||||
window.onbeforeunload = () => {
|
|
||||||
return 'Are you Sure?';
|
|
||||||
};
|
|
||||||
|
|
||||||
this.parseUrl();
|
|
||||||
|
|
||||||
console.log('Mounted <App>!');
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
parseUrl() {
|
|
||||||
const loc = location.href.split('/');
|
|
||||||
|
|
||||||
console.log(loc);
|
|
||||||
|
|
||||||
switch (loc[3].replace(location.search, '')) {
|
|
||||||
case '':
|
|
||||||
case 'search':
|
|
||||||
this.search = loc[4];
|
|
||||||
console.log(loc[4], this.search);
|
|
||||||
break;
|
|
||||||
case 'watch':
|
|
||||||
this.getSong(loc[3]);
|
|
||||||
console.log(loc[3]);
|
|
||||||
break;
|
|
||||||
case 'playlist':
|
|
||||||
this.getAlbum(loc[3]);
|
|
||||||
console.log(loc[3]);
|
|
||||||
break;
|
|
||||||
case 'channel':
|
|
||||||
this.getArtist(loc[4]);
|
|
||||||
console.log(loc[4]);
|
|
||||||
default:
|
|
||||||
console.log(loc);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Toggle(e) {
|
|
||||||
console.log(this[e]);
|
|
||||||
|
|
||||||
if (this[e]) {
|
|
||||||
this[e] = false;
|
|
||||||
} else {
|
|
||||||
this[e] = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Update(e) {
|
|
||||||
this.search = e;
|
|
||||||
console.log('update');
|
|
||||||
},
|
|
||||||
timeUpdate(t) {
|
|
||||||
this.time = Math.floor((t / this.duration) * 100);
|
|
||||||
},
|
|
||||||
async getJson(url) {
|
|
||||||
const res = await fetch(url).then((res) => res.json());
|
|
||||||
|
|
||||||
if (!res.error) {
|
|
||||||
return res;
|
|
||||||
} else {
|
|
||||||
alert(
|
|
||||||
res.message
|
|
||||||
.replaceAll('Video', 'Audio')
|
|
||||||
.replaceAll('video', 'audio')
|
|
||||||
.replaceAll('watched', 'heard'),
|
|
||||||
);
|
|
||||||
console.error(res.message);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setSong(s) {
|
|
||||||
this.urls = [s];
|
|
||||||
|
|
||||||
this.playNext();
|
|
||||||
},
|
|
||||||
addSong(s) {
|
|
||||||
this.urls.push(s);
|
|
||||||
|
|
||||||
const index = this.urls.map((s) => s.url).indexOf(this.url);
|
|
||||||
|
|
||||||
if (
|
|
||||||
(index == this.urls.length - 1 && this.time > 98) ||
|
|
||||||
this.urls.length == 1
|
|
||||||
) {
|
|
||||||
console.log(true);
|
|
||||||
this.playNext();
|
|
||||||
} else {
|
|
||||||
console.log(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(s, this.urls);
|
|
||||||
},
|
|
||||||
playThis(t) {
|
|
||||||
const i = this.urls.indexOf(t);
|
|
||||||
this.getSong(this.urls[i].url);
|
|
||||||
},
|
|
||||||
playList(a) {
|
|
||||||
this.urls = a;
|
|
||||||
|
|
||||||
this.getSong(this.urls[0].url);
|
|
||||||
},
|
|
||||||
playNext(u) {
|
|
||||||
if (this.hls) {
|
|
||||||
this.hls.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
const i = this.urls.map((s) => s.url).indexOf(this.url),
|
|
||||||
next = this.urls[i + 1];
|
|
||||||
|
|
||||||
console.log('Index: ' + i);
|
|
||||||
console.log(this.url, this.urls, next);
|
|
||||||
|
|
||||||
if (this.urls.length > i && this.urls.length != 0 && next) {
|
|
||||||
this.getSong(next.url);
|
|
||||||
} else if (this.loop) {
|
|
||||||
this.url = this.urls[0].url;
|
|
||||||
this.getSong(this.urls[0].url);
|
|
||||||
} else {
|
|
||||||
this.urls = [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async getSong(e) {
|
|
||||||
console.log(e);
|
|
||||||
|
|
||||||
const hash = new URLSearchParams(e.substring(e.indexOf('?'))).get('v'),
|
|
||||||
json = await this.getJson(
|
|
||||||
'https://pipedapi.kavin.rocks/streams/' + hash,
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(json);
|
|
||||||
|
|
||||||
this.Stream({
|
|
||||||
art: json.thumbnailUrl,
|
|
||||||
artist: json.uploader,
|
|
||||||
time: json.duration,
|
|
||||||
hls: json.hls,
|
|
||||||
stream: json.audioStreams[0].url,
|
|
||||||
title: json.title,
|
|
||||||
url: e,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
async getAlbum(e) {
|
|
||||||
console.log('Album: ', e);
|
|
||||||
|
|
||||||
const hash = new URLSearchParams(e.substring(e.indexOf('?'))).get('list'),
|
|
||||||
json = await this.getJson(
|
|
||||||
'https://pipedapi.kavin.rocks/playlists/' + hash,
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(json, json.relatedStreams);
|
|
||||||
|
|
||||||
this.songItems = {
|
|
||||||
items: json.relatedStreams,
|
|
||||||
title: json.name,
|
|
||||||
};
|
|
||||||
|
|
||||||
history.pushState({}, '', e);
|
|
||||||
|
|
||||||
this.artist = {
|
|
||||||
playlistId: null,
|
|
||||||
title: null,
|
|
||||||
description: null,
|
|
||||||
subscriberCount: 0,
|
|
||||||
thumbnails: [],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
async getArtist(e) {
|
|
||||||
console.log(e);
|
|
||||||
|
|
||||||
const json = await this.getJson(
|
|
||||||
'https://hyperpipeapi.onrender.com/channel/' + e,
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(json);
|
|
||||||
|
|
||||||
this.items = json.items;
|
|
||||||
this.items.notes = json.playlistId;
|
|
||||||
json.items = null;
|
|
||||||
this.artist = json;
|
|
||||||
|
|
||||||
history.pushState({}, '', '/channel/' + e);
|
|
||||||
},
|
|
||||||
setVolume(vol) {
|
|
||||||
this.$refs.audio.volume = vol;
|
|
||||||
},
|
|
||||||
playPause() {
|
|
||||||
if (this.$refs.audio.paused) {
|
|
||||||
this.$refs.audio.play();
|
|
||||||
this.state = 'pause';
|
|
||||||
} else {
|
|
||||||
this.$refs.audio.pause();
|
|
||||||
this.state = 'play';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Stream(res) {
|
|
||||||
console.log(res);
|
|
||||||
|
|
||||||
this.art = res.art;
|
|
||||||
this.cover = `--art: url(${res.art});`;
|
|
||||||
this.nowtitle = res.title;
|
|
||||||
this.nowartist = res.artist.split(' - ')[0];
|
|
||||||
this.duration = res.time;
|
|
||||||
this.url = res.url;
|
|
||||||
|
|
||||||
if (!!Hls && Hls.isSupported()) {
|
|
||||||
this.hls = new Hls();
|
|
||||||
|
|
||||||
console.log(this.hls.levels);
|
|
||||||
this.hls.loadSource(res.hls);
|
|
||||||
this.hls.attachMedia(this.$refs.audio);
|
|
||||||
} else {
|
|
||||||
this.audioSrc = res.stream;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
audioCanPlay() {
|
|
||||||
this.lazyLoad();
|
|
||||||
this.$refs.audio.play();
|
|
||||||
this.state = 'pause';
|
|
||||||
|
|
||||||
if (location.pathname != '/playlist') {
|
|
||||||
history.pushState({}, '', this.url);
|
|
||||||
}
|
|
||||||
|
|
||||||
document.title = `Playing: ${this.nowtitle} by ${this.nowartist}`;
|
|
||||||
|
|
||||||
this.setMetadata(this.art);
|
|
||||||
},
|
|
||||||
setMetadata(art) {
|
|
||||||
if (navigator.mediaSession) {
|
|
||||||
navigator.mediaSession.metadata = new MediaMetadata({
|
|
||||||
title: this.nowtitle,
|
|
||||||
artist: this.nowartist,
|
|
||||||
artwork: [
|
|
||||||
{
|
|
||||||
src: art,
|
|
||||||
type: 'image/png',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
lazyLoad() {
|
|
||||||
let lazyElems;
|
|
||||||
|
|
||||||
if ('IntersectionObserver' in window) {
|
|
||||||
lazyElems = document.querySelectorAll('.bg-img:not(.lazy)');
|
|
||||||
|
|
||||||
let imgObs = new IntersectionObserver((elems, obs) => {
|
|
||||||
elems.forEach((elem) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
if (elem.isIntersecting) {
|
|
||||||
let ele = elem.target;
|
|
||||||
|
|
||||||
ele.classList.add('lazy');
|
|
||||||
imgObs.unobserve(ele);
|
|
||||||
}
|
|
||||||
}, 20);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
lazyElems.forEach((img) => {
|
|
||||||
imgObs.observe(img);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log('Failed');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@import './assets/base.css';
|
@import './assets/base.css';
|
||||||
|
|
||||||
|
@ -405,22 +405,7 @@ header {
|
||||||
height: 175px;
|
height: 175px;
|
||||||
width: 175px;
|
width: 175px;
|
||||||
}
|
}
|
||||||
.bg-img {
|
|
||||||
background-image: linear-gradient(45deg, #88c0d0, #5e81ac);
|
|
||||||
background-position: center;
|
|
||||||
background-size: cover;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
}
|
|
||||||
.bg-img.lazy {
|
|
||||||
background-image: var(--art);
|
|
||||||
}
|
|
||||||
.search-artists {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.search-artists .bg-img {
|
|
||||||
border-radius: 50%;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
img,
|
img,
|
||||||
.card,
|
.card,
|
||||||
.card-bg {
|
.card-bg {
|
||||||
|
@ -436,21 +421,6 @@ a,
|
||||||
color: var(--color-foreground);
|
color: var(--color-foreground);
|
||||||
transition: 0.4s;
|
transition: 0.4s;
|
||||||
}
|
}
|
||||||
button {
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
color: var(--color-text);
|
|
||||||
appearence: none;
|
|
||||||
}
|
|
||||||
.bi {
|
|
||||||
color: var(--color-text);
|
|
||||||
font-size: 1.25rem;
|
|
||||||
transition: color 0.3s ease;
|
|
||||||
}
|
|
||||||
.bi:hover,
|
|
||||||
.bi.true {
|
|
||||||
color: var(--color-foreground);
|
|
||||||
}
|
|
||||||
.flex {
|
.flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -463,49 +433,7 @@ button {
|
||||||
.flex .bi {
|
.flex .bi {
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
}
|
}
|
||||||
.caps {
|
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
|
||||||
.pop {
|
|
||||||
--shadow: none;
|
|
||||||
--translate: 0;
|
|
||||||
}
|
|
||||||
.pop,
|
|
||||||
.pop-2 {
|
|
||||||
transition: box-shadow 0.4s ease, transform 0.4s ease;
|
|
||||||
}
|
|
||||||
.pop:hover {
|
|
||||||
--shadow: 0.5rem 0.5rem 1rem var(--color-shadow);
|
|
||||||
--translate: -1.25rem;
|
|
||||||
box-shadow: var(--shadow);
|
|
||||||
transform: translateX(calc(var(--translate) / 2))
|
|
||||||
translateY(calc(var(--translate) / 2));
|
|
||||||
}
|
|
||||||
.pop-2 {
|
|
||||||
transform: translateX(var(--translate)) translateY(var(--translate));
|
|
||||||
box-shadow: var(--shadow);
|
|
||||||
}
|
|
||||||
.popup-wrap {
|
|
||||||
--display: none;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.popup-wrap:hover,
|
|
||||||
.popup-wrap:focus,
|
|
||||||
.popup:focus,
|
|
||||||
.popup:active {
|
|
||||||
--display: flex;
|
|
||||||
}
|
|
||||||
.popup {
|
|
||||||
position: absolute;
|
|
||||||
display: var(--display);
|
|
||||||
background-color: var(--color-background);
|
|
||||||
padding: 0.5rem;
|
|
||||||
border-radius: 0.125rem;
|
|
||||||
z-index: 999;
|
|
||||||
bottom: 1.25rem;
|
|
||||||
box-shadow: 0 0 0.5rem var(--color-border);
|
|
||||||
animation: fade 0.4s ease;
|
|
||||||
}
|
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
a:hover {
|
a:hover {
|
||||||
background-color: var(--color-border);
|
background-color: var(--color-border);
|
||||||
|
@ -532,21 +460,4 @@ button {
|
||||||
margin: 0 2rem 0 0;
|
margin: 0 2rem 0 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fade {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes fill {
|
|
||||||
from {
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
width: var(--width);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -114,6 +114,129 @@ body {
|
||||||
height: 0.75rem;
|
height: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
*:focus {
|
||||||
text-align: center;
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--color-text);
|
||||||
|
appearence: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Components and Helpers */
|
||||||
|
|
||||||
|
.bi {
|
||||||
|
color: var(--color-text);
|
||||||
|
font-size: 1.25rem;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
.bi:hover,
|
||||||
|
.bi.true {
|
||||||
|
color: var(--color-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.caps {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-img {
|
||||||
|
background-image: linear-gradient(45deg, #88c0d0, #5e81ac);
|
||||||
|
background-position: center;
|
||||||
|
background-size: cover;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
.bg-img.lazy {
|
||||||
|
background-image: var(--art);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pop {
|
||||||
|
--shadow: none;
|
||||||
|
--translate: 0;
|
||||||
|
}
|
||||||
|
.pop,
|
||||||
|
.pop-2 {
|
||||||
|
transition: box-shadow 0.4s ease, transform 0.4s ease;
|
||||||
|
}
|
||||||
|
.pop:hover {
|
||||||
|
--shadow: 0.5rem 0.5rem 1rem var(--color-shadow);
|
||||||
|
--translate: -1.25rem;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
transform: translateX(calc(var(--translate) / 2))
|
||||||
|
translateY(calc(var(--translate) / 2));
|
||||||
|
}
|
||||||
|
.pop-2 {
|
||||||
|
transform: translateX(var(--translate)) translateY(var(--translate));
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-wrap {
|
||||||
|
--display: none;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.popup-wrap:hover,
|
||||||
|
.popup-wrap:focus,
|
||||||
|
.popup:focus,
|
||||||
|
.popup:active {
|
||||||
|
--display: flex;
|
||||||
|
}
|
||||||
|
.popup {
|
||||||
|
position: absolute;
|
||||||
|
display: var(--display);
|
||||||
|
background-color: var(--color-background);
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 0.125rem;
|
||||||
|
z-index: 999;
|
||||||
|
bottom: 1.25rem;
|
||||||
|
box-shadow: 0 0 0.5rem var(--color-border);
|
||||||
|
animation: fade 0.4s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bars-wrap {
|
||||||
|
position: absolute;
|
||||||
|
height: 1.5rem;
|
||||||
|
width: 2rem;
|
||||||
|
transform: rotateZ(180deg);
|
||||||
|
}
|
||||||
|
.bars {
|
||||||
|
position: relative;
|
||||||
|
height: 15%;
|
||||||
|
width: calc(calc(100% / 3) - 0.2rem);
|
||||||
|
margin-left: 0.1rem;
|
||||||
|
background: var(--color-foreground);
|
||||||
|
float: left;
|
||||||
|
animation: heightc 1s ease infinite;
|
||||||
|
}
|
||||||
|
.bars:first-child {
|
||||||
|
animation-delay: 0.25s;
|
||||||
|
}
|
||||||
|
.bars:nth-child(2) {
|
||||||
|
animation-delay: 0.5s;
|
||||||
|
}
|
||||||
|
.bars:last-child {
|
||||||
|
margin-left: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
@keyframes fade {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes fill {
|
||||||
|
from {
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
width: var(--width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes heightc {
|
||||||
|
50% {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,12 @@ defineProps({
|
||||||
default: '--art: linear-gradient(45deg, #88c0d0, #5e81ac);',
|
default: '--art: linear-gradient(45deg, #88c0d0, #5e81ac);',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['open-album']);
|
||||||
|
|
||||||
|
function onClick() {
|
||||||
|
emit('open-album');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -20,16 +26,6 @@ defineProps({
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
methods: {
|
|
||||||
onClick() {
|
|
||||||
this.$emit('open-album');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.card {
|
.card {
|
||||||
min-height: 17rem;
|
min-height: 17rem;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue';
|
|
||||||
import PlayBtn from './PlayBtn.vue';
|
import PlayBtn from './PlayBtn.vue';
|
||||||
|
|
||||||
defineProps(['title', 'desc', 'subs', 'thumbs', 'play']);
|
defineProps(['title', 'desc', 'subs', 'thumbs', 'play']);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
defineEmits(['update-search']);
|
|
||||||
import SearchBar from '../components/SearchBar.vue';
|
import SearchBar from '../components/SearchBar.vue';
|
||||||
|
|
||||||
|
defineEmits(['update-search']);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
defineProps(['url', 'urls', 'show']);
|
defineProps({
|
||||||
|
url: String,
|
||||||
|
urls: Array,
|
||||||
|
show: Boolean,
|
||||||
|
});
|
||||||
defineEmits(['playthis']);
|
defineEmits(['playthis']);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -56,34 +60,4 @@ defineEmits(['playthis']);
|
||||||
.pl-main {
|
.pl-main {
|
||||||
padding-left: 3rem;
|
padding-left: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bars-wrap {
|
|
||||||
position: absolute;
|
|
||||||
height: 1.5rem;
|
|
||||||
width: 2rem;
|
|
||||||
transform: rotateZ(180deg);
|
|
||||||
}
|
|
||||||
.bars {
|
|
||||||
position: relative;
|
|
||||||
height: 15%;
|
|
||||||
width: calc(calc(100% / 3) - 0.2rem);
|
|
||||||
margin-left: 0.1rem;
|
|
||||||
background: var(--color-foreground);
|
|
||||||
float: left;
|
|
||||||
animation: heightc 1s ease infinite;
|
|
||||||
}
|
|
||||||
.bars:first-child {
|
|
||||||
animation-delay: 0.25s;
|
|
||||||
}
|
|
||||||
.bars:nth-child(2) {
|
|
||||||
animation-delay: 0.5s;
|
|
||||||
}
|
|
||||||
.bars:last-child {
|
|
||||||
margin-left: none;
|
|
||||||
}
|
|
||||||
@keyframes heightc {
|
|
||||||
50% {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,44 +1,141 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
defineProps([
|
import { reactive, watch } from 'vue';
|
||||||
'search',
|
import PlayBtn from './PlayBtn.vue';
|
||||||
'songItems',
|
import SongItem from './SongItem.vue';
|
||||||
'items'
|
import AlbumItem from './AlbumItem.vue';
|
||||||
]);
|
|
||||||
defineEmits([
|
import { getJsonPiped, getPipedQuery } from '../scripts/fetch.js'
|
||||||
'get-song',
|
|
||||||
|
const props = defineProps(['search', 'songItems', 'items']);
|
||||||
|
|
||||||
|
const emit = defineEmits([
|
||||||
'get-album',
|
'get-album',
|
||||||
'get-artist',
|
'get-artist',
|
||||||
'lazy',
|
'lazy',
|
||||||
'play-urls',
|
'play-urls',
|
||||||
'add-song',
|
'add-song',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const data = reactive({
|
||||||
|
notes: null,
|
||||||
|
albums: null,
|
||||||
|
albumTitle: null,
|
||||||
|
songs: null,
|
||||||
|
recommendedArtists: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
function Reset() {
|
||||||
|
for (let i in data) {
|
||||||
|
data[i] = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function playAlbum() {
|
||||||
|
const urls = data.songs.items.map((item) => {
|
||||||
|
return { url: item.url, title: item.title };
|
||||||
|
});
|
||||||
|
|
||||||
|
emit('play-urls', urls);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSearch(q) {
|
||||||
|
if (q) {
|
||||||
|
const pq = q.split(' ').join('+');
|
||||||
|
|
||||||
|
history.pushState({}, '', `/search/${pq + getPipedQuery()}`);
|
||||||
|
|
||||||
|
document.title = 'Search Results for ' + q;
|
||||||
|
|
||||||
|
getResults(pq);
|
||||||
|
emit('lazy');
|
||||||
|
} else {
|
||||||
|
Reset();
|
||||||
|
|
||||||
|
history.pushState({}, '', '/');
|
||||||
|
document.title = 'Hyperpipe';
|
||||||
|
|
||||||
|
console.log('No Search');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getResults(q) {
|
||||||
|
const filters = ['music_songs', 'music_albums'];
|
||||||
|
|
||||||
|
for (let filter of filters) {
|
||||||
|
|
||||||
|
const json = await getJsonPiped(`/search?q=${q}&filter=${filter}`);
|
||||||
|
|
||||||
|
data[filter.split('_')[1]] = json;
|
||||||
|
console.log(json, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.search,
|
||||||
|
(n) => {
|
||||||
|
if (n) {
|
||||||
|
n = n.replace(location.search || '', '');
|
||||||
|
|
||||||
|
console.log(n);
|
||||||
|
getSearch(n);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.songItems,
|
||||||
|
(i) => {
|
||||||
|
console.log(i);
|
||||||
|
|
||||||
|
Reset();
|
||||||
|
|
||||||
|
data.songs = {}
|
||||||
|
data.songs.items = i.items;
|
||||||
|
data.albumTitle = i.title;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.items,
|
||||||
|
(itms) => {
|
||||||
|
Reset();
|
||||||
|
|
||||||
|
for (let i in itms) {
|
||||||
|
|
||||||
|
data[i] = {}
|
||||||
|
data[i].items = itms[i];
|
||||||
|
|
||||||
|
console.log(data[i]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="songs && songs.corrected" class="text-full">
|
<div v-if="data.songs && data.songs.corrected" class="text-full">
|
||||||
I Fixed your Typo, "<span class="caps">{{ songs.suggestion }}</span
|
I Fixed your Typo, "<span class="caps">{{ data.songs.suggestion }}</span
|
||||||
>"!!
|
>"!!
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="albumTitle" class="text-full flex">
|
<div v-if="data.albumTitle" class="text-full flex">
|
||||||
<PlayBtn @click="playAlbum" />
|
<PlayBtn @click="playAlbum" />
|
||||||
<span>{{ albumTitle }}</span>
|
<span>{{ data.albumTitle }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="songs && songs.items[0]" class="search-songs">
|
<div v-if="data.songs && data.songs.items[0]" class="search-songs">
|
||||||
<h2>Top Songs</h2>
|
<h2>Top Songs</h2>
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<template v-for="song in songs.items">
|
<template v-for="song in data.songs.items">
|
||||||
<SongItem
|
<SongItem
|
||||||
:author="song.uploaderName || ''"
|
:author="song.uploaderName || ''"
|
||||||
:title="song.title || song.name"
|
:title="song.title || song.name"
|
||||||
:channel="song.uploaderUrl || ''"
|
:channel="song.uploaderUrl || ''"
|
||||||
:play="song.url || '/watch?v=' + song.videoId"
|
:play="song.url || '/watch?v=' + song.id"
|
||||||
@open-song="
|
@open-song="
|
||||||
$emit('get-song', {
|
$emit('play-urls', [{
|
||||||
url: song.url || '/watch?v=' + song.id,
|
url: song.url || '/watch?v=' + song.id,
|
||||||
title: song.title || song.name,
|
title: song.title || song.name,
|
||||||
})
|
}])
|
||||||
"
|
"
|
||||||
@get-artist="
|
@get-artist="
|
||||||
(e) => {
|
(e) => {
|
||||||
|
@ -56,17 +153,18 @@ defineEmits([
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
v-if="notes"
|
v-if="data.notes"
|
||||||
@click.prevent="$emit('get-album', '/playlist?list=' + notes.items)"
|
@click.prevent="$emit('get-album', '/playlist?list=' + data.notes.items)"
|
||||||
class="more"
|
class="more"
|
||||||
|
:href="'/playlist?list=' + data.notes.items"
|
||||||
>See All</a
|
>See All</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="albums && albums.items[0]" class="search-albums">
|
<div v-if="data.albums && data.albums.items[0]" class="search-albums">
|
||||||
<h2>Albums</h2>
|
<h2>Albums</h2>
|
||||||
<div class="grid-3">
|
<div class="grid-3">
|
||||||
<template v-for="album in albums.items">
|
<template v-for="album in data.albums.items">
|
||||||
<AlbumItem
|
<AlbumItem
|
||||||
:author="album.uploaderName || album.subtitle"
|
:author="album.uploaderName || album.subtitle"
|
||||||
:name="album.name || album.title"
|
:name="album.name || album.title"
|
||||||
|
@ -81,11 +179,11 @@ defineEmits([
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="recommendedArtists && recommendedArtists.items[0]"
|
v-if="data.recommendedArtists && data.recommendedArtists.items[0]"
|
||||||
class="search-artists">
|
class="search-artists">
|
||||||
<h2>Similar Artists</h2>
|
<h2>Similar Artists</h2>
|
||||||
<div class="grid-3">
|
<div class="grid-3">
|
||||||
<template v-for="artist in recommendedArtists.items">
|
<template v-for="artist in data.recommendedArtists.items">
|
||||||
<AlbumItem
|
<AlbumItem
|
||||||
:author="artist.subtitle"
|
:author="artist.subtitle"
|
||||||
:name="artist.title"
|
:name="artist.title"
|
||||||
|
@ -96,99 +194,6 @@ defineEmits([
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import PlayBtn from './PlayBtn.vue';
|
|
||||||
import SongItem from './SongItem.vue';
|
|
||||||
import AlbumItem from './AlbumItem.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
PlayBtn,
|
|
||||||
SongItem,
|
|
||||||
AlbumItem,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
songs: null,
|
|
||||||
albums: null,
|
|
||||||
recommendedArtists: null,
|
|
||||||
albumTitle: null,
|
|
||||||
notes: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
search(NewSearch) {
|
|
||||||
console.log(NewSearch);
|
|
||||||
this.getSearch(NewSearch);
|
|
||||||
},
|
|
||||||
songItems(i) {
|
|
||||||
console.log(i);
|
|
||||||
|
|
||||||
this.Reset();
|
|
||||||
|
|
||||||
this.songs = {};
|
|
||||||
this.songs.items = i.items;
|
|
||||||
this.albumTitle = i.title;
|
|
||||||
},
|
|
||||||
items(itms) {
|
|
||||||
this.Reset();
|
|
||||||
|
|
||||||
for (let i in itms) {
|
|
||||||
this[i] = {};
|
|
||||||
this[i].items = itms[i];
|
|
||||||
|
|
||||||
console.log(this[i]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
Reset() {
|
|
||||||
this.notes = null;
|
|
||||||
this.albums = null;
|
|
||||||
this.albumTitle = null;
|
|
||||||
this.songs = null;
|
|
||||||
this.recommendedArtists = null;
|
|
||||||
},
|
|
||||||
playAlbum() {
|
|
||||||
const urls = this.songs.items.map((item) => {
|
|
||||||
return { url: item.url, title: item.title };
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$emit('play-urls', urls);
|
|
||||||
},
|
|
||||||
getSearch(q) {
|
|
||||||
if (q) {
|
|
||||||
history.pushState({}, '', `/search/${q}`);
|
|
||||||
|
|
||||||
document.title = 'Search Results for ' + q;
|
|
||||||
|
|
||||||
this.getResults(q.split(' ').join('+'));
|
|
||||||
this.$emit('lazy');
|
|
||||||
} else {
|
|
||||||
this.Reset();
|
|
||||||
history.pushState({}, '', '/');
|
|
||||||
document.title = 'Hyperpipe';
|
|
||||||
|
|
||||||
console.log('No Search');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getResults(q) {
|
|
||||||
const filters = ['music_songs', 'music_albums'];
|
|
||||||
|
|
||||||
for (let filter of filters) {
|
|
||||||
fetch(
|
|
||||||
`https://pipedapi.kavin.rocks/search?q=${q}&filter=${filter}`,
|
|
||||||
).then((res) => {
|
|
||||||
res.json().then((json) => {
|
|
||||||
this[filter.split('_')[1]] = json;
|
|
||||||
console.log(json);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.search-albums,
|
.search-albums,
|
||||||
.search-songs,
|
.search-songs,
|
||||||
|
@ -196,11 +201,23 @@ export default {
|
||||||
place-items: start center;
|
place-items: start center;
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
.search-songs h2,
|
||||||
|
.search-artists h2,
|
||||||
|
.search-albums h2 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
.search-albums .grid-3,
|
.search-albums .grid-3,
|
||||||
.search-artists .grid-3 {
|
.search-artists .grid-3 {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
.search-artists {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.search-artists .bg-img {
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
.text-full {
|
.text-full {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
|
|
|
@ -1,11 +1,43 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
defineProps({
|
const props = defineProps({
|
||||||
author: String,
|
author: String,
|
||||||
title: String,
|
title: String,
|
||||||
channel: String,
|
channel: String,
|
||||||
play: String,
|
play: String,
|
||||||
});
|
});
|
||||||
defineEmits(['get-artist']);
|
|
||||||
|
const emit = defineEmits(['get-artist', 'open-song']);
|
||||||
|
|
||||||
|
function openSong(el) {
|
||||||
|
if (!el.classList.contains('ign')) {
|
||||||
|
emit('open-song', props.play);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function Share() {
|
||||||
|
if ('share' in navigator) {
|
||||||
|
const data = {
|
||||||
|
title: `Listen to ${props.title} by ${props.author} on Hyperpipe`,
|
||||||
|
url: location.origin + props.play,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.share(data);
|
||||||
|
console.log('Done Sharing!');
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
navigator.clipboard.writeText(location.host + props.play).then(
|
||||||
|
() => {
|
||||||
|
console.log('Copied to Clipboard');
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
console.log(err);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="song card flex pop" @click="openSong($event.target)">
|
<div class="song card flex pop" @click="openSong($event.target)">
|
||||||
|
@ -35,42 +67,6 @@ defineEmits(['get-artist']);
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
methods: {
|
|
||||||
openSong(el) {
|
|
||||||
if (!el.classList.contains('ign')) {
|
|
||||||
this.$emit('open-song', this.play);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async Share() {
|
|
||||||
if ('share' in navigator) {
|
|
||||||
const data = {
|
|
||||||
title: `Listen to ${this.title} by ${this.author} on Hyperpipe`,
|
|
||||||
url: location.origin + this.play,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
await navigator.share(data);
|
|
||||||
console.log('Done Sharing!');
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
navigator.clipboard.writeText(location.host + this.play).then(
|
|
||||||
() => {
|
|
||||||
console.log('Copied to Clipboard');
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
console.log(err);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.card {
|
.card {
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
const props = defineProps({
|
defineProps({
|
||||||
state: String,
|
state: String,
|
||||||
time: Number,
|
time: Number,
|
||||||
show: Boolean,
|
show: Boolean,
|
||||||
loop: Boolean,
|
loop: Boolean,
|
||||||
});
|
});
|
||||||
defineEmits(['vol', 'play', 'list', 'loop']);
|
defineEmits(['vol', 'play', 'list', 'loop', 'change-time']);
|
||||||
|
|
||||||
console.log(props);
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div id="statusbar" class="flex">
|
<div id="statusbar" class="flex">
|
||||||
|
@ -19,9 +17,14 @@ console.log(props);
|
||||||
|
|
||||||
<div
|
<div
|
||||||
id="statusbar-progress"
|
id="statusbar-progress"
|
||||||
class="progress"
|
class="range-wrap"
|
||||||
:style="'--tw: ' + time + '%;'">
|
:style="'--fw:' + time + '%;'">
|
||||||
<div class="progress-thumb"></div>
|
<input
|
||||||
|
:value="time"
|
||||||
|
type="range"
|
||||||
|
name="statusbar-progress"
|
||||||
|
max="100"
|
||||||
|
@input="$emit('change-time', $event.target.value)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -77,38 +80,6 @@ console.log(props);
|
||||||
.bi-infinity {
|
.bi-infinity {
|
||||||
font-size: 1.75rem !important;
|
font-size: 1.75rem !important;
|
||||||
}
|
}
|
||||||
.progress {
|
|
||||||
--h: 0.25rem;
|
|
||||||
--w: 5rem;
|
|
||||||
--th: 100%;
|
|
||||||
--tw: 75%;
|
|
||||||
background: var(--color-border);
|
|
||||||
height: var(--h);
|
|
||||||
width: var(--w);
|
|
||||||
transition: width 0.4s ease;
|
|
||||||
}
|
|
||||||
.progress.v {
|
|
||||||
--th: 75%;
|
|
||||||
--tw: 100%;
|
|
||||||
--h: 5rem;
|
|
||||||
--w: 0.25rem;
|
|
||||||
transform: rotateZ(180deg);
|
|
||||||
z-index: 999999;
|
|
||||||
}
|
|
||||||
.progress-thumb {
|
|
||||||
width: var(--tw);
|
|
||||||
height: var(--th);
|
|
||||||
background: var(--color-foreground);
|
|
||||||
overflow: hidden;
|
|
||||||
transition: width 0.4s ease;
|
|
||||||
}
|
|
||||||
.progress-thumb:after {
|
|
||||||
content: '';
|
|
||||||
background-color: #fff;
|
|
||||||
height: 0.5rem;
|
|
||||||
width: 0.5rem;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
.popup {
|
.popup {
|
||||||
--h: 6.5rem;
|
--h: 6.5rem;
|
||||||
--w: 1rem;
|
--w: 1rem;
|
||||||
|
@ -125,6 +96,7 @@ input[type='range'] {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: transparent;
|
color: transparent;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
input[type='range']:focus {
|
input[type='range']:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
@ -132,16 +104,49 @@ input[type='range']:focus {
|
||||||
input[type='range']::-webkit-slider-thumb {
|
input[type='range']::-webkit-slider-thumb {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.4s ease;
|
||||||
|
}
|
||||||
|
input[type='range']:hover::-webkit-slider-thumb,
|
||||||
|
#vol input[type='range']::-webkit-slider-thumb {
|
||||||
background-color: var(--color-foreground);
|
background-color: var(--color-foreground);
|
||||||
|
opacity: 1;
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
margin-top: -0.45rem;
|
margin-top: -0.45rem;
|
||||||
}
|
}
|
||||||
input[type='range']::-webkit-slider-runnable-track {
|
input[type='range']::-webkit-slider-runnable-track {
|
||||||
height: 0.1rem;
|
height: 100%;
|
||||||
background-color: var(--color-border);
|
background-color: var(--color-border);
|
||||||
}
|
}
|
||||||
|
#vol input[type='range']::-webkit-slider-runnable-track {
|
||||||
|
height: 0.1rem;
|
||||||
|
}
|
||||||
|
.range-wrap {
|
||||||
|
--fw: 0%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
height: 0.25rem;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.range-wrap:before {
|
||||||
|
content: '';
|
||||||
|
width: var(--fw);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: 0.25rem;
|
||||||
|
background-color: var(--color-foreground);
|
||||||
|
transition: width 0.4s ease;
|
||||||
|
}
|
||||||
|
.range-wrap input[type='range'] {
|
||||||
|
--w: 100%;
|
||||||
|
--h: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
#statusbar-progress {
|
#statusbar-progress {
|
||||||
width: 50vw;
|
width: 50vw;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
|
|
34
src/scripts/fetch.js
Normal file
34
src/scripts/fetch.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
export function getPipedQuery() {
|
||||||
|
const papi = new URLSearchParams(location.search).get('pipedapi')
|
||||||
|
|
||||||
|
if (!papi) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return '?pipedapi=' + papi
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getJson(url) {
|
||||||
|
const res = await fetch(url).then((res) => res.json()).catch(err => { alert(err) });
|
||||||
|
|
||||||
|
if (!res.error) {
|
||||||
|
return res;
|
||||||
|
} else {
|
||||||
|
alert(
|
||||||
|
res.message
|
||||||
|
.replaceAll('Video', 'Audio')
|
||||||
|
.replaceAll('video', 'audio')
|
||||||
|
.replaceAll('watched', 'heard'),
|
||||||
|
);
|
||||||
|
console.error(res.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getJsonPiped(path) {
|
||||||
|
const root =
|
||||||
|
new URLSearchParams(location.search).get('pipedapi') ||
|
||||||
|
localStorage.getItem('pipedapi') ||
|
||||||
|
'pipedapi.kavin.rocks';
|
||||||
|
|
||||||
|
return await getJson('https://' + root + path);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue