Preferences + Format code.

This commit is contained in:
FireMasterK 2020-11-17 10:45:35 +05:30
parent e853cb9840
commit 4891c57bbd
11 changed files with 379 additions and 219 deletions

1
.gitignore vendored
View File

@ -2,7 +2,6 @@
node_modules node_modules
/dist /dist
# local env files # local env files
.env.local .env.local
.env.*.local .env.*.local

View File

@ -1,24 +1,29 @@
# piped # piped
## Project setup ## Project setup
``` ```
npm install npm install
``` ```
### Compiles and hot-reloads for development ### Compiles and hot-reloads for development
``` ```
npm run serve npm run serve
``` ```
### Compiles and minifies for production ### Compiles and minifies for production
``` ```
npm run build npm run build
``` ```
### Lints and fixes files ### Lints and fixes files
``` ```
npm run lint npm run lint
``` ```
### Customize configuration ### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/). See [Configuration Reference](https://cli.vuejs.org/config/).

View File

@ -1,33 +1,43 @@
<template> <template>
<div class="uk-container uk-container-expand uk-light uk-height-viewport" style="background:#0b0e0f"> <div
<nav class="uk-navbar-container uk-container-expand uk-light" style="background:#0b0e0f" uk-navbar> class="uk-container uk-container-expand uk-light uk-height-viewport"
<div class="uk-navbar-left"> style="background: #0b0e0f"
<router-link class="uk-navbar-item uk-logo uk-text-bold" to="/">Piped</router-link> >
</div> <nav
<div class="uk-navbar-right"> class="uk-navbar-container uk-container-expand uk-light"
<ul class="uk-navbar-nav"> style="background: #0b0e0f"
<li> uk-navbar
<router-link to="/login">Login</router-link> >
</li> <div class="uk-navbar-left">
<li> <router-link class="uk-navbar-item uk-logo uk-text-bold" to="/"
<router-link to="/feed">Feed</router-link> >Piped</router-link
</li> >
</ul> </div>
</div> <div class="uk-navbar-right">
</nav> <ul class="uk-navbar-nav">
<li>
<router-link to="/preferences">Preferences</router-link>
</li>
<li>
<router-link to="/login">Login</router-link>
</li>
<li>
<router-link to="/feed">Feed</router-link>
</li>
</ul>
</div>
</nav>
<router-view /> <router-view />
</div> </div>
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {};
}
} }
} };
</script> </script>
<style> <style>

View File

@ -1,3 +1,3 @@
export default { export default {
BASE_URL: 'https://pipedapi.kavin.rocks', BASE_URL: localStorage.getItem("instance") || 'https://pipedapi.kavin.rocks',
} }

View File

@ -1,22 +1,32 @@
<template> <template>
<div v-if="channel"> <div v-if="channel">
<h1 class="uk-text-center"><img v-bind:src="channel.avatarUrl">{{ channel.name }}</h1> <h1 class="uk-text-center">
<img v-bind:src="channel.bannerUrl" style="width: 100%"> <img v-bind:src="channel.avatarUrl" />{{ channel.name }}
<p v-html="this.channel.description.replaceAll('\n', '<br>')"></p> </h1>
<img v-bind:src="channel.bannerUrl" style="width: 100%" />
<p v-html="this.channel.description.replaceAll('\n', '<br>')"></p>
<hr> <hr />
<div class="uk-grid-small" style="width: 100%" uk-grid="parallax: 0"> <div class="uk-grid-small" style="width: 100%" uk-grid="parallax: 0">
<div style="width: 288px" v-bind:key="item.url" v-for="item in this.channel.relatedStreams"> <div
<router-link class="uk-link-muted" style="height: 100px" v-bind:to="item.url || '/'"> style="width: 288px"
<img style="width: 100%" v-bind:src="item.thumbnail"> v-bind:key="item.url"
<a>{{ item.title }}</a> v-for="item in this.channel.relatedStreams"
</router-link> >
<br> <router-link
<a>{{ timeFormat(item.duration) }}</a> class="uk-link-muted"
style="height: 100px"
v-bind:to="item.url || '/'"
>
<img style="width: 100%" v-bind:src="item.thumbnail" />
<a>{{ item.title }}</a>
</router-link>
<br />
<a>{{ timeFormat(item.duration) }}</a>
</div>
</div> </div>
</div> </div>
</div>
</template> </template>
<script> <script>
@ -25,27 +35,31 @@ import Constants from "@/Constants.js";
export default { export default {
data() { data() {
return { return {
channel: null, channel: null
} };
}, },
mounted() { mounted() {
this.getChannelData() this.getChannelData();
}, },
methods: { methods: {
async fetchChannel() { async fetchChannel() {
return await ( return await (
await fetch(Constants.BASE_URL + "/channels/" + this.$route.params.channelId) await fetch(
Constants.BASE_URL +
"/channels/" +
this.$route.params.channelId
)
).json(); ).json();
}, },
async getChannelData() { async getChannelData() {
this.fetchChannel().then(data => this.channel = data) this.fetchChannel()
.then(() => document.title = this.channel.name + " - Piped") .then(data => (this.channel = data))
.then(() => (document.title = this.channel.name + " - Piped"));
}, },
timeFormat(duration) { timeFormat(duration) {
var pad = function(num, size) {
var pad = function (num, size) { return ("000" + num).slice(size * -1);
return ('000' + num).slice(size * -1); };
}
var time = parseFloat(duration).toFixed(3), var time = parseFloat(duration).toFixed(3),
hours = Math.floor(time / 60 / 60), hours = Math.floor(time / 60 / 60),
@ -54,14 +68,12 @@ export default {
var str = ""; var str = "";
if (hours > 0) if (hours > 0) str += pad(hours, 2) + ":";
str += pad(hours, 2) + ":"
str += pad(minutes, 2) + ':' + pad(seconds, 2) str += pad(minutes, 2) + ":" + pad(seconds, 2);
return str; return str;
} }
} }
} };
</script> </script>

View File

@ -0,0 +1,79 @@
<template>
<table class="uk-table">
<thead>
<tr>
<th>Instance Name</th>
<th>Instance Locations</th>
<th>Has CDN?</th>
</tr>
</thead>
<tbody v-bind:key="instance.name" v-for="instance in instances">
<tr>
<td>{{ instance.name }}</td>
<td>{{ instance.locations }}</td>
<td>{{ instance.cdn }}</td>
</tr>
</tbody>
</table>
<select
class="uk-select"
v-model="selectedInstance"
@change="onChange($event)"
>
<option
v-bind:key="instance.name"
v-for="instance in instances"
v-bind:value="instance.apiurl"
>
{{ instance.name }}
</option>
</select>
</template>
<script>
export default {
data() {
return {
selectedInstance: null,
instances: []
};
},
mounted() {
fetch(
"https://raw.githubusercontent.com/wiki/TeamPiped/Piped-Frontend/Instances.md"
)
.then(resp => resp.text())
.then(body => {
var skipped = 0;
const lines = body.split("\n");
lines.map(line => {
const split = line.split("|");
if (split.length == 4) {
if (skipped < 2) {
skipped++;
return;
}
this.instances.push({
name: split[0].trim(),
apiurl: split[1].trim(),
locations: split[2].trim(),
cdn: split[3].trim()
});
}
});
});
if (localStorage)
this.selectedInstance =
localStorage.getItem("instance") ||
"https://pipedapi.kavin.rocks";
},
methods: {
onChange() {
if (localStorage)
localStorage.setItem("instance", this.selectedInstance);
}
}
};
</script>

View File

@ -1,45 +1,58 @@
<template> <template>
<h1 class="uk-text-bold uk-text-center">Trending</h1> <h1 class="uk-text-bold uk-text-center">Trending</h1>
<hr> <hr />
<div class="uk-grid-collapse" style="width: 100%" uk-grid="parallax: 0"> <div class="uk-grid-collapse" style="width: 100%" uk-grid="parallax: 0">
<div class="uk-tile-default" style="width: 300px; background: #0b0e0f" v-bind:key="video.url" v-for="video in videos"> <div
<div class="uk-card uk-card-default uk-card-body uk-grid-match uk-text-secondary" style="background: #0b0e0f"> class="uk-tile-default"
<router-link class="uk-text-emphasis" v-bind:to="video.url || '/'"> style="width: 300px; background: #0b0e0f"
<p>{{ video.title }}</p> v-bind:key="video.url"
<img style="width: 100%" v-bind:src="video.thumbnail" /> v-for="video in videos"
</router-link> >
<router-link class="uk-link-muted" v-bind:to="video.uploaderUrl || '/'"> <div
<p>{{ video.uploaderName }}</p> class="uk-card uk-card-default uk-card-body uk-grid-match uk-text-secondary"
</router-link> style="background: #0b0e0f"
<font-awesome-icon icon="eye"></font-awesome-icon> {{ video.views }} views >
</div> <router-link
</div> class="uk-text-emphasis"
</div> v-bind:to="video.url || '/'"
</template> >
<p>{{ video.title }}</p>
<script> <img style="width: 100%" v-bind:src="video.thumbnail" />
import Constants from '@/Constants.js' </router-link>
<router-link
export default { class="uk-link-muted"
data() { v-bind:to="video.uploaderUrl || '/'"
return { >
videos: [], <p>{{ video.uploaderName }}</p>
}; </router-link>
}, <font-awesome-icon icon="eye"></font-awesome-icon>
mounted() { {{ video.views }} views
</div>
document.title = "Trending - Piped"; </div>
</div>
this.fetchTrending().then(videos => this.videos = videos) </template>
},
methods: { <script>
async fetchTrending() { import Constants from "@/Constants.js";
return await (
await fetch(Constants.BASE_URL + "/trending") export default {
).json(); data() {
} return {
} videos: []
} };
</script> },
mounted() {
document.title = "Trending - Piped";
console.log(Constants.BASE_URL);
this.fetchTrending().then(videos => (this.videos = videos));
},
methods: {
async fetchTrending() {
return await (await fetch(Constants.BASE_URL + "/trending")).json();
}
}
};
</script>

View File

@ -1,42 +1,56 @@
<template> <template>
<div class="uk-container uk-container-xlarge"> <div class="uk-container uk-container-xlarge">
<h1 class="uk-text-bold">{{ video.title }}</h1> <h1 class="uk-text-bold">{{ video.title }}</h1>
<video controls ref="player" class="video-js preview-player-dimensions "></video> <video
controls
ref="player"
class="video-js preview-player-dimensions"
></video>
<img :src="video.uploaderAvatar" /> <img :src="video.uploaderAvatar" />
<router-link class="uk-text-bold" v-bind:to="video.uploaderUrl || '/'"> <router-link class="uk-text-bold" v-bind:to="video.uploaderUrl || '/'">
<a>{{ video.uploader }}</a> <a>{{ video.uploader }}</a>
</router-link>
<p class="uk-dark">
<font-awesome-icon icon="thumbs-down"></font-awesome-icon>
{{ video.likes }}
<font-awesome-icon icon="thumbs-up"></font-awesome-icon>
{{ video.dislikes }}
</p>
<p>
<font-awesome-icon icon="eye"></font-awesome-icon> {{ video.views }} views
</p>
<p class="uk-light" v-html="video.description"></p>
<a v-if="sponsors && sponsors.segments">Sponsors Segments: {{ sponsors.segments.length }}</a>
<hr>
<div class="uk-tile-default uk-text-secondary" style="background: #0b0e0f; width: 300px" v-bind:key="related.url" v-for="related in video.relatedStreams">
<router-link class="uk-link-muted" v-bind:to="related.url">
<p class="uk-text-emphasis">{{ related.title }}</p>
<img style="width: 100%" v-bind:src="related.thumbnail" />
</router-link> </router-link>
<p>
<router-link class="uk-link-muted" v-bind:to="related.uploaderUrl || '/'">
<p>{{ related.uploaderName }}</p>
</router-link>
<font-awesome-icon icon="eye"></font-awesome-icon>
{{ related.views }} views
</p>
</div>
</div> <p class="uk-dark">
<font-awesome-icon icon="thumbs-down"></font-awesome-icon>
{{ video.likes }}
<font-awesome-icon icon="thumbs-up"></font-awesome-icon>
{{ video.dislikes }}
</p>
<p>
<font-awesome-icon icon="eye"></font-awesome-icon>
{{ video.views }} views
</p>
<p class="uk-light" v-html="video.description"></p>
<a v-if="sponsors && sponsors.segments"
>Sponsors Segments: {{ sponsors.segments.length }}</a
>
<hr />
<div
class="uk-tile-default uk-text-secondary"
style="background: #0b0e0f; width: 300px"
v-bind:key="related.url"
v-for="related in video.relatedStreams"
>
<router-link class="uk-link-muted" v-bind:to="related.url">
<p class="uk-text-emphasis">{{ related.title }}</p>
<img style="width: 100%" v-bind:src="related.thumbnail" />
</router-link>
<p>
<router-link
class="uk-link-muted"
v-bind:to="related.uploaderUrl || '/'"
>
<p>{{ related.uploaderName }}</p>
</router-link>
<font-awesome-icon icon="eye"></font-awesome-icon>
{{ related.views }} views
</p>
</div>
</div>
</template> </template>
<script> <script>
@ -52,11 +66,11 @@ export default {
data() { data() {
return { return {
video: { video: {
title: "Loading...", title: "Loading..."
}, },
player: null, player: null,
audioplayer: null, audioplayer: null,
sponsors: null, sponsors: null
}; };
}, },
mounted() { mounted() {
@ -68,37 +82,45 @@ export default {
this.player.dispose(); this.player.dispose();
} }
if (this.audioplayer) { if (this.audioplayer) {
this.audioplayer.pause() this.audioplayer.pause();
} }
}, },
watch: { watch: {
"$route.query.v": function (v) { "$route.query.v": function(v) {
if (v) { if (v) {
if (this.audioplayer) if (this.audioplayer) this.audioplayer.pause();
this.audioplayer.pause()
this.getVideoData(); this.getVideoData();
this.getSponsors(); this.getSponsors();
} }
}, }
}, },
methods: { methods: {
async fetchVideo() { async fetchVideo() {
return await ( return await (
await fetch(Constants.BASE_URL + "/streams/" + this.$route.query.v) await fetch(
Constants.BASE_URL + "/streams/" + this.$route.query.v
)
).json(); ).json();
}, },
async fetchSponsors() { async fetchSponsors() {
return await ( return await (
await fetch(Constants.BASE_URL + "/sponsors/" + this.$route.query.v + "?category=[\"sponsor\",\"interaction\",\"selfpromo\",\"music_offtopic\"]") await fetch(
Constants.BASE_URL +
"/sponsors/" +
this.$route.query.v +
'?category=["sponsor","interaction","selfpromo","music_offtopic"]'
)
).json(); ).json();
}, },
async getVideoData() { async getVideoData() {
this.fetchVideo() this.fetchVideo()
.then((data) => (this.video = data)) .then(data => (this.video = data))
.then(() => { .then(() => {
document.title = this.video.title + " - Piped"; document.title = this.video.title + " - Piped";
this.video.description = this.video.description.replaceAll("http://www.youtube.com", "").replaceAll("https://www.youtube.com", "") this.video.description = this.video.description
.replaceAll("http://www.youtube.com", "")
.replaceAll("https://www.youtube.com", "");
const options = { const options = {
autoplay: false, autoplay: false,
@ -110,22 +132,23 @@ export default {
"volumePanel", "volumePanel",
"qualitySelector", "qualitySelector",
"captionsButton", "captionsButton",
"fullscreenToggle", "fullscreenToggle"
], ]
}, },
responsive: false, responsive: false,
aspectRatio: '16:9' aspectRatio: "16:9"
}; };
const noPrevPlayer = !this.player const noPrevPlayer = !this.player;
if (noPrevPlayer) this.player = videojs(this.$refs.player, options); if (noPrevPlayer)
this.player = videojs(this.$refs.player, options);
this.player.hotkeys({ this.player.hotkeys({
volumeStep: 0.1, volumeStep: 0.1,
seekStep: 5, seekStep: 5,
enableModifiersForNumbers: false, enableModifiersForNumbers: false,
enableHoverScroll: true, enableHoverScroll: true
}); });
this.player.poster(this.video.thumbnailUrl); this.player.poster(this.video.thumbnailUrl);
@ -133,96 +156,110 @@ export default {
var src = []; var src = [];
if (this.video.livestream) { if (this.video.livestream) {
src.push({ src.push({
src: this.video.hls, src: this.video.hls,
type: 'application/x-mpegURL' type: "application/x-mpegURL"
}) });
} else { } else {
this.video.videoStreams.map((stream) => this.video.videoStreams.map(stream =>
src.push({ src.push({
src: stream.url, src: stream.url,
type: stream.mimeType, type: stream.mimeType,
label: stream.quality, label: stream.quality
}) })
); );
this.video.audioStreams.map((stream) => this.video.audioStreams.map(stream =>
src.push({ src.push({
src: stream.url, src: stream.url,
type: stream.mimeType, type: stream.mimeType,
label: stream.quality, label: stream.quality
}) })
); );
} }
this.audioplayer = new Audio((this.video.audioStreams.slice(-1)[0].url)); this.audioplayer = new Audio(
this.video.audioStreams.slice(-1)[0].url
);
this.player.src(src); this.player.src(src);
if (noPrevPlayer) { if (noPrevPlayer) {
this.player.on('timeupdate', () => { this.player.on("timeupdate", () => {
if (this.sponsors && this.sponsors.segments) { if (this.sponsors && this.sponsors.segments) {
const time = this.player.currentTime() const time = this.player.currentTime();
this.sponsors.segments.map(segment => { this.sponsors.segments.map(segment => {
if (!segment.skipped) { if (!segment.skipped) {
const end = segment.segment[1] const end = segment.segment[1];
if (time >= segment.segment[0] && time < end) { if (
this.player.currentTime(end) time >= segment.segment[0] &&
this.audioplayer.currentTime = end time < end
segment.skipped = true ) {
return this.player.currentTime(end);
this.audioplayer.currentTime = end;
segment.skipped = true;
return;
} }
} }
}) });
} }
if (this.audioplayer) { if (this.audioplayer) {
const delay =
const delay = this.audioplayer.currentTime - this.player.currentTime() this.audioplayer.currentTime -
this.player.currentTime();
if (Math.abs(delay) > 0.1) { if (Math.abs(delay) > 0.1) {
this.audioplayer.currentTime = this.player.currentTime() - delay this.audioplayer.currentTime =
this.player.currentTime() - delay;
} }
} }
}); });
this.player.on('play', () => { this.player.on("play", () => {
this.audioplayer.play() this.audioplayer.play();
}); });
this.player.on('pause', () => { this.player.on("pause", () => {
this.audioplayer.currentTime = this.player.currentTime() this.audioplayer.currentTime = this.player.currentTime();
this.audioplayer.pause() this.audioplayer.pause();
}); });
this.player.on('volumechange', () => { this.player.on("volumechange", () => {
this.audioplayer.volume = this.player.volume() this.audioplayer.volume = this.player.volume();
}) });
} }
if (!noPrevPlayer) if (!noPrevPlayer)
this.player.remoteTextTracks().map(track => this.player.removeRemoteTextTrack(track)); this.player
.remoteTextTracks()
.map(track =>
this.player.removeRemoteTextTrack(track)
);
this.video.subtitles.map(subtitle => { this.video.subtitles.map(subtitle => {
this.player.addRemoteTextTrack({ this.player.addRemoteTextTrack(
kind: "captions", {
src: subtitle.url.replace("fmt=ttml", "fmt=vtt"), kind: "captions",
label: "Track", src: subtitle.url.replace(
type: "captions/captions.vtt" "fmt=ttml",
}, false).mode = "showing" "fmt=vtt"
}) ),
label: "Track",
type: "captions/captions.vtt"
},
false
).mode = "showing";
});
//const parent = this.player.el().querySelector(".vjs-progress-holder")
//TODO: Add sponsors on seekbar: https://github.com/ajayyy/SponsorBlock/blob/e39de9fd852adb9196e0358ed827ad38d9933e29/src/js-components/previewBar.ts#L12 //TODO: Add sponsors on seekbar: https://github.com/ajayyy/SponsorBlock/blob/e39de9fd852adb9196e0358ed827ad38d9933e29/src/js-components/previewBar.ts#L12
}); });
}, },
async getSponsors() { async getSponsors() {
this.fetchSponsors().then((data) => (this.sponsors = data)); this.fetchSponsors().then(data => (this.sponsors = data));
}, }
}, }
}; };
</script> </script>

View File

@ -1,18 +1,18 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { faThumbsUp, faThumbsDown, faEye } from '@fortawesome/free-solid-svg-icons' import { faThumbsUp, faThumbsDown, faEye } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
library.add(faThumbsUp, faThumbsDown, faEye) library.add(faThumbsUp, faThumbsDown, faEye)
require("uikit/src/less/uikit.less") require("uikit/src/less/uikit.less")
require("uikit/dist/js/uikit.min.js") require("uikit/dist/js/uikit.min.js")
import router from '@/router/router' import router from '@/router/router'
import App from './App.vue' import App from './App.vue'
import './registerServiceWorker' import './registerServiceWorker'
const app = createApp(App) const app = createApp(App)
app.use(router) app.use(router)
app.component('font-awesome-icon', FontAwesomeIcon) app.component('font-awesome-icon', FontAwesomeIcon)
app.mount('#app') app.mount('#app')

View File

@ -21,7 +21,7 @@ if (process.env.NODE_ENV === 'production') {
}, },
updated() { updated() {
console.log('New content is available; please refresh.') console.log('New content is available; please refresh.')
caches.keys().then(function(names) { caches.keys().then(function (names) {
for (let name of names) caches.delete(name); for (let name of names) caches.delete(name);
}) })
}, },

View File

@ -2,6 +2,7 @@ import { createRouter, createWebHistory } from 'vue-router'
import Watch from '../components/WatchVideo.vue' import Watch from '../components/WatchVideo.vue'
import Trending from '../components/TrendingPage.vue' import Trending from '../components/TrendingPage.vue'
import Channel from '../components/Channel.vue' import Channel from '../components/Channel.vue'
import Preferences from '../components/Preferences.vue'
const routes = [{ const routes = [{
path: '/watch', path: '/watch',
@ -15,6 +16,10 @@ const routes = [{
path: '/channel/:channelId', path: '/channel/:channelId',
name: 'Channel', name: 'Channel',
component: Channel component: Channel
}, {
path: '/preferences',
name: 'Preferences',
component: Preferences
}] }]
const router = createRouter({ const router = createRouter({
@ -22,4 +27,4 @@ const router = createRouter({
routes routes
}) })
export default router export default router