Merge pull request #960 from TeamPiped/playwith

Implement play with playlists.
This commit is contained in:
Kavin 2022-04-08 22:38:37 +01:00 committed by GitHub
commit 128d39903d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 153 additions and 5 deletions

View File

@ -75,6 +75,7 @@ export default {
}, },
activated() { activated() {
window.addEventListener("scroll", this.handleScroll); window.addEventListener("scroll", this.handleScroll);
if (this.playlist) this.updateTitle();
}, },
deactivated() { deactivated() {
window.removeEventListener("scroll", this.handleScroll); window.removeEventListener("scroll", this.handleScroll);
@ -86,7 +87,10 @@ export default {
async getPlaylistData() { async getPlaylistData() {
this.fetchPlaylist() this.fetchPlaylist()
.then(data => (this.playlist = data)) .then(data => (this.playlist = data))
.then(() => (document.title = this.playlist.name + " - Piped")); .then(() => this.updateTitle());
},
async updateTitle() {
document.title = this.playlist.name + " - Piped";
}, },
handleScroll() { handleScroll() {
if (this.loading || !this.playlist || !this.playlist.nextpage) return; if (this.loading || !this.playlist || !this.playlist.nextpage) return;

View File

@ -0,0 +1,57 @@
<template>
<div class="overflow-x-scroll h-screen-sm" ref="scrollable">
<VideoItem
v-for="(related, index) in playlist.relatedStreams"
:key="related.url"
:video="related"
:index="index"
:playlist-id="playlistId"
height="94"
width="168"
/>
</div>
</template>
<script>
import { nextTick } from "vue";
import VideoItem from "./VideoItem.vue";
export default {
components: { VideoItem },
props: {
playlist: {
type: Object,
required: true,
},
playlistId: {
type: String,
required: true,
},
selectedIndex: {
type: Number,
},
},
mounted() {
this.updateScroll();
},
methods: {
updateScroll() {
const elems = Array.from(this.$refs.scrollable.children).filter(elm => elm.matches("div"));
const index = this.selectedIndex - 1;
if (index < elems.length)
this.$refs.scrollable.scrollTop =
elems[this.selectedIndex - 1].offsetTop - this.$refs.scrollable.offsetTop;
},
},
watch: {
playlist: {
handler() {
if (this.selectedIndex - 1 < this.playlist.relatedStreams.length)
nextTick(() => {
this.updateScroll();
});
},
deep: true,
},
},
};
</script>

View File

@ -1,6 +1,15 @@
<template> <template>
<div> <div>
<router-link :to="video.url"> <router-link
:to="{
path: '/watch',
query: {
v: video.url.substr(-11),
...(playlistId && { list: playlistId }),
...(index >= 0 && { index: index + 1 }),
},
}"
>
<img :height="height" :width="width" class="w-full" :src="video.thumbnail" alt="" loading="lazy" /> <img :height="height" :width="width" class="w-full" :src="video.thumbnail" alt="" loading="lazy" />
<div class="relative text-sm"> <div class="relative text-sm">
<span <span
@ -26,7 +35,15 @@
<div class="float-right m-0 inline-block children:px-1"> <div class="float-right m-0 inline-block children:px-1">
<router-link <router-link
:to="video.url + '&listen=1'" :to="{
path: '/watch',
query: {
v: video.url.substr(-11),
...(playlistId && { list: playlistId }),
...(index >= 0 && { index: index + 1 }),
listen: '1',
},
}"
:aria-label="'Listen to ' + video.title" :aria-label="'Listen to ' + video.title"
:title="'Listen to ' + video.title" :title="'Listen to ' + video.title"
> >

View File

@ -24,6 +24,14 @@ export default {
return {}; return {};
}, },
}, },
playlist: {
type: Object,
default: null,
},
index: {
type: Number,
default: -1,
},
sponsors: { sponsors: {
type: Object, type: Object,
default: () => { default: () => {
@ -38,6 +46,7 @@ export default {
return { return {
lastUpdate: new Date().getTime(), lastUpdate: new Date().getTime(),
initialSeekComplete: false, initialSeekComplete: false,
destroying: false,
}; };
}, },
computed: { computed: {
@ -171,9 +180,11 @@ export default {
}); });
}, },
deactivated() { deactivated() {
this.destroying = true;
this.destroy(true); this.destroy(true);
}, },
unmounted() { unmounted() {
this.destroying = true;
this.destroy(true); this.destroy(true);
}, },
methods: { methods: {
@ -281,6 +292,7 @@ export default {
if (noPrevPlayer) if (noPrevPlayer)
this.shakaPromise.then(() => { this.shakaPromise.then(() => {
if (this.destroying) return;
this.shaka.polyfill.installAll(); this.shaka.polyfill.installAll();
const localPlayer = new this.shaka.Player(videoEl); const localPlayer = new this.shaka.Player(videoEl);
@ -355,13 +367,23 @@ export default {
videoEl.addEventListener("ended", () => { videoEl.addEventListener("ended", () => {
if (!this.selectedAutoLoop && this.selectedAutoPlay && this.video.relatedStreams.length > 0) { if (!this.selectedAutoLoop && this.selectedAutoPlay && this.video.relatedStreams.length > 0) {
const params = this.$route.query; const params = this.$route.query;
let url = this.video.relatedStreams[0].url; let url = this.playlist?.relatedStreams?.[this.index]?.url ?? this.video.relatedStreams[0].url;
const searchParams = new URLSearchParams(); const searchParams = new URLSearchParams();
for (var param in params) for (var param in params)
switch (param) { switch (param) {
case "v": case "v":
case "t": case "t":
break; break;
case "index":
if (this.index < this.playlist.relatedStreams.length)
searchParams.set("index", this.index + 1);
break;
case "list":
console.log(this.index);
console.log(this.playlist.relatedStreams.length);
if (this.index < this.playlist.relatedStreams.length)
searchParams.set("list", params.list);
break;
default: default:
searchParams.set(param, params[param]); searchParams.set(param, params[param]);
break; break;

View File

@ -7,7 +7,7 @@ export default {
activated() { activated() {
const videoId = this.$route.params.videoId; const videoId = this.$route.params.videoId;
if (videoId) if (videoId)
this.$router.push({ this.$router.replace({
path: "/watch", path: "/watch",
query: { v: videoId }, query: { v: videoId },
}); });

View File

@ -4,6 +4,8 @@
ref="videoPlayer" ref="videoPlayer"
:video="video" :video="video"
:sponsors="sponsors" :sponsors="sponsors"
:playlist="playlist"
:index="index"
:selected-auto-play="false" :selected-auto-play="false"
:selected-auto-loop="selectedAutoLoop" :selected-auto-loop="selectedAutoLoop"
:is-embed="isEmbed" :is-embed="isEmbed"
@ -18,6 +20,8 @@
ref="videoPlayer" ref="videoPlayer"
:video="video" :video="video"
:sponsors="sponsors" :sponsors="sponsors"
:playlist="playlist"
:index="index"
:selected-auto-play="selectedAutoPlay" :selected-auto-play="selectedAutoPlay"
:selected-auto-loop="selectedAutoLoop" :selected-auto-loop="selectedAutoLoop"
/> />
@ -128,6 +132,12 @@
</div> </div>
<div v-if="video" class="order-first sm:order-last"> <div v-if="video" class="order-first sm:order-last">
<PlaylistVideos
v-if="playlist"
:playlist-id="playlistId"
:playlist="playlist"
:selected-index="index"
/>
<a <a
class="btn mb-2 sm:hidden" class="btn mb-2 sm:hidden"
@click="showRecs = !showRecs" @click="showRecs = !showRecs"
@ -156,6 +166,7 @@ import ErrorHandler from "./ErrorHandler.vue";
import CommentItem from "./CommentItem.vue"; import CommentItem from "./CommentItem.vue";
import Chapters from "./Chapters.vue"; import Chapters from "./Chapters.vue";
import PlaylistAddModal from "./PlaylistAddModal.vue"; import PlaylistAddModal from "./PlaylistAddModal.vue";
import PlaylistVideos from "./PlaylistVideos.vue";
export default { export default {
name: "App", name: "App",
@ -166,6 +177,7 @@ export default {
CommentItem, CommentItem,
Chapters, Chapters,
PlaylistAddModal, PlaylistAddModal,
PlaylistVideos,
}, },
data() { data() {
const smallViewQuery = window.matchMedia("(max-width: 640px)"); const smallViewQuery = window.matchMedia("(max-width: 640px)");
@ -173,6 +185,9 @@ export default {
video: { video: {
title: "Loading...", title: "Loading...",
}, },
playlistId: null,
playlist: null,
index: null,
sponsors: null, sponsors: null,
selectedAutoLoop: false, selectedAutoLoop: false,
selectedAutoPlay: null, selectedAutoPlay: null,
@ -237,6 +252,9 @@ export default {
})(); })();
if (this.active) this.$refs.videoPlayer.loadVideo(); if (this.active) this.$refs.videoPlayer.loadVideo();
}); });
this.playlistId = this.$route.query.list;
this.index = Number(this.$route.query.index);
this.getPlaylistData();
this.getSponsors(); this.getSponsors();
if (!this.isEmbed && this.getPreferenceBoolean("comments", true)) this.getComments(); if (!this.isEmbed && this.getPreferenceBoolean("comments", true)) this.getComments();
window.addEventListener("resize", () => { window.addEventListener("resize", () => {
@ -307,6 +325,36 @@ export default {
} }
}); });
}, },
async getPlaylistData() {
if (this.playlistId) {
await this.fetchJson(this.apiUrl() + "/playlists/" + this.playlistId).then(data => {
this.playlist = data;
});
await this.fetchPlaylistPages().then(() => {
if (!(this.index >= 0)) {
for (let i = 0; i < this.playlist.relatedStreams.length; i++)
if (this.playlist.relatedStreams[i].url.substr(-11) == this.getVideoId()) {
this.index = i + 1;
this.$router.replace({
query: { ...this.$route.query, index: this.index },
});
break;
}
}
});
}
},
async fetchPlaylistPages() {
if (this.playlist.nextpage) {
await this.fetchJson(this.apiUrl() + "/nextpage/playlists/" + this.playlistId, {
nextpage: this.playlist.nextpage,
}).then(json => {
this.playlist.relatedStreams = this.playlist.relatedStreams.concat(json.relatedStreams);
this.playlist.nextpage = json.nextpage;
});
await this.fetchPlaylistPages();
}
},
async getSponsors() { async getSponsors() {
if (this.getPreferenceBoolean("sponsorblock", true)) if (this.getPreferenceBoolean("sponsorblock", true))
this.fetchSponsors().then(data => (this.sponsors = data)); this.fetchSponsors().then(data => (this.sponsors = data));