mirror of
https://github.com/TeamPiped/Piped.git
synced 2024-11-14 12:18:24 +00:00
Merge pull request #2553 from Bnyro/local-playlists
Playlists without an account
This commit is contained in:
commit
c1c8faaf5b
@ -55,7 +55,7 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if ("indexedDB" in window) {
|
if ("indexedDB" in window) {
|
||||||
const request = indexedDB.open("piped-db", 4);
|
const request = indexedDB.open("piped-db", 5);
|
||||||
request.onupgradeneeded = ev => {
|
request.onupgradeneeded = ev => {
|
||||||
const db = request.result;
|
const db = request.result;
|
||||||
console.log("Upgrading object store.");
|
console.log("Upgrading object store.");
|
||||||
@ -77,6 +77,12 @@ export default {
|
|||||||
const store = db.createObjectStore("channel_groups", { keyPath: "groupName" });
|
const store = db.createObjectStore("channel_groups", { keyPath: "groupName" });
|
||||||
store.createIndex("groupName", "groupName", { unique: true });
|
store.createIndex("groupName", "groupName", { unique: true });
|
||||||
}
|
}
|
||||||
|
if (!db.objectStoreNames.contains("playlists")) {
|
||||||
|
const playlistStore = db.createObjectStore("playlists", { keyPath: "playlistId" });
|
||||||
|
playlistStore.createIndex("playlistId", "playlistId", { unique: true });
|
||||||
|
const playlistVideosStore = db.createObjectStore("playlist_videos", { keyPath: "videoId" });
|
||||||
|
playlistVideosStore.createIndex("videoId", "videoId", { unique: true });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
request.onsuccess = e => {
|
request.onsuccess = e => {
|
||||||
window.db = e.target.result;
|
window.db = e.target.result;
|
||||||
|
@ -23,6 +23,10 @@ export default {
|
|||||||
ModalComponent,
|
ModalComponent,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
videoInfo: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
videoId: {
|
videoId: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
@ -62,28 +66,14 @@ export default {
|
|||||||
this.$refs.addButton.disabled = true;
|
this.$refs.addButton.disabled = true;
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
this.fetchJson(this.authApiUrl() + "/user/playlists/add", null, {
|
this.addVideosToPlaylist(playlistId, [this.videoId], [this.videoInfo]).then(json => {
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({
|
|
||||||
playlistId: playlistId,
|
|
||||||
videoId: this.videoId,
|
|
||||||
}),
|
|
||||||
headers: {
|
|
||||||
Authorization: this.getAuthToken(),
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
}).then(json => {
|
|
||||||
this.setPreference("selectedPlaylist" + this.hashCode(this.authApiUrl()), playlistId);
|
this.setPreference("selectedPlaylist" + this.hashCode(this.authApiUrl()), playlistId);
|
||||||
this.$emit("close");
|
this.$emit("close");
|
||||||
if (json.error) alert(json.error);
|
if (json.error) alert(json.error);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async fetchPlaylists() {
|
async fetchPlaylists() {
|
||||||
this.fetchJson(this.authApiUrl() + "/user/playlists", null, {
|
this.getPlaylists().then(json => {
|
||||||
headers: {
|
|
||||||
Authorization: this.getAuthToken(),
|
|
||||||
},
|
|
||||||
}).then(json => {
|
|
||||||
this.playlists = json;
|
this.playlists = json;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -86,14 +86,11 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
const playlistId = this.$route.query.list;
|
const playlistId = this.$route.query.list;
|
||||||
if (this.authenticated && playlistId?.length == 36)
|
if (this.authenticated && playlistId?.length == 36)
|
||||||
this.fetchJson(this.authApiUrl() + "/user/playlists", null, {
|
this.getPlaylists().then(json => {
|
||||||
headers: {
|
|
||||||
Authorization: this.getAuthToken(),
|
|
||||||
},
|
|
||||||
}).then(json => {
|
|
||||||
if (json.error) alert(json.error);
|
if (json.error) alert(json.error);
|
||||||
else if (json.some(playlist => playlist.id === playlistId)) this.admin = true;
|
else if (json.some(playlist => playlist.id === playlistId)) this.admin = true;
|
||||||
});
|
});
|
||||||
|
else if (playlistId.startsWith("local")) this.admin = true;
|
||||||
this.isPlaylistBookmarked();
|
this.isPlaylistBookmarked();
|
||||||
},
|
},
|
||||||
activated() {
|
activated() {
|
||||||
@ -106,6 +103,11 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async fetchPlaylist() {
|
async fetchPlaylist() {
|
||||||
|
const playlistId = this.$route.query.list;
|
||||||
|
if (playlistId.startsWith("local")) {
|
||||||
|
return this.getPlaylist(playlistId);
|
||||||
|
}
|
||||||
|
|
||||||
return await await this.fetchJson(this.authApiUrl() + "/playlists/" + this.$route.query.list);
|
return await await this.fetchJson(this.authApiUrl() + "/playlists/" + this.$route.query.list);
|
||||||
},
|
},
|
||||||
async getPlaylistData() {
|
async getPlaylistData() {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<h2 v-if="authenticated" class="font-bold my-4" v-t="'titles.playlists'" />
|
<h2 class="font-bold my-4" v-t="'titles.playlists'" />
|
||||||
|
|
||||||
<div v-if="authenticated" class="flex justify-between mb-3">
|
<div class="flex justify-between mb-3">
|
||||||
<button v-t="'actions.create_playlist'" class="btn" @click="onCreatePlaylist" />
|
<button v-t="'actions.create_playlist'" class="btn" @click="onCreatePlaylist" />
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<button
|
<button
|
||||||
@ -63,7 +63,7 @@
|
|||||||
v-if="playlistToDelete == playlist.id"
|
v-if="playlistToDelete == playlist.id"
|
||||||
:message="$t('actions.delete_playlist_confirm')"
|
:message="$t('actions.delete_playlist_confirm')"
|
||||||
@close="playlistToDelete = null"
|
@close="playlistToDelete = null"
|
||||||
@confirm="deletePlaylist(playlist.id)"
|
@confirm="onDeletePlaylist(playlist.id)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -115,7 +115,7 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.authenticated) this.fetchPlaylists();
|
this.fetchPlaylists();
|
||||||
this.loadPlaylistBookmarks();
|
this.loadPlaylistBookmarks();
|
||||||
},
|
},
|
||||||
activated() {
|
activated() {
|
||||||
@ -123,11 +123,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchPlaylists() {
|
fetchPlaylists() {
|
||||||
this.fetchJson(this.authApiUrl() + "/user/playlists", null, {
|
this.getPlaylists().then(json => {
|
||||||
headers: {
|
|
||||||
Authorization: this.getAuthToken(),
|
|
||||||
},
|
|
||||||
}).then(json => {
|
|
||||||
this.playlists = json;
|
this.playlists = json;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -141,50 +137,21 @@ export default {
|
|||||||
const newName = this.newPlaylistName;
|
const newName = this.newPlaylistName;
|
||||||
const newDescription = this.newPlaylistDescription;
|
const newDescription = this.newPlaylistDescription;
|
||||||
if (newName != selectedPlaylist.name) {
|
if (newName != selectedPlaylist.name) {
|
||||||
this.fetchJson(this.authApiUrl() + "/user/playlists/rename", null, {
|
this.renamePlaylist(selectedPlaylist.id, newName).then(json => {
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({
|
|
||||||
playlistId: selectedPlaylist.id,
|
|
||||||
newName: newName,
|
|
||||||
}),
|
|
||||||
headers: {
|
|
||||||
Authorization: this.getAuthToken(),
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
}).then(json => {
|
|
||||||
if (json.error) alert(json.error);
|
if (json.error) alert(json.error);
|
||||||
else selectedPlaylist.name = newName;
|
else selectedPlaylist.name = newName;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (newDescription != selectedPlaylist.description) {
|
if (newDescription != selectedPlaylist.description) {
|
||||||
this.fetchJson(this.authApiUrl() + "/user/playlists/description", null, {
|
this.changePlaylistDescription(selectedPlaylist.id, newDescription).then(json => {
|
||||||
method: "PATCH",
|
|
||||||
body: JSON.stringify({
|
|
||||||
playlistId: selectedPlaylist.id,
|
|
||||||
description: newDescription,
|
|
||||||
}),
|
|
||||||
headers: {
|
|
||||||
Authorization: this.getAuthToken(),
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
}).then(json => {
|
|
||||||
if (json.error) alert(json.error);
|
if (json.error) alert(json.error);
|
||||||
else selectedPlaylist.description = newDescription;
|
else selectedPlaylist.description = newDescription;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.playlistToEdit = null;
|
this.playlistToEdit = null;
|
||||||
},
|
},
|
||||||
deletePlaylist(id) {
|
onDeletePlaylist(id) {
|
||||||
this.fetchJson(this.authApiUrl() + "/user/playlists/delete", null, {
|
this.deletePlaylist(id).then(json => {
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({
|
|
||||||
playlistId: id,
|
|
||||||
}),
|
|
||||||
headers: {
|
|
||||||
Authorization: this.getAuthToken(),
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
}).then(json => {
|
|
||||||
if (json.error) alert(json.error);
|
if (json.error) alert(json.error);
|
||||||
else this.playlists = this.playlists.filter(playlist => playlist.id !== id);
|
else this.playlists = this.playlists.filter(playlist => playlist.id !== id);
|
||||||
});
|
});
|
||||||
@ -198,19 +165,6 @@ export default {
|
|||||||
else this.fetchPlaylists();
|
else this.fetchPlaylists();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async createPlaylist(name) {
|
|
||||||
let json = await this.fetchJson(this.authApiUrl() + "/user/playlists/create", null, {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({
|
|
||||||
name: name,
|
|
||||||
}),
|
|
||||||
headers: {
|
|
||||||
Authorization: this.getAuthToken(),
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return json;
|
|
||||||
},
|
|
||||||
async exportPlaylists() {
|
async exportPlaylists() {
|
||||||
if (!this.playlists) return;
|
if (!this.playlists) return;
|
||||||
let json = {
|
let json = {
|
||||||
@ -223,8 +177,8 @@ export default {
|
|||||||
this.download(JSON.stringify(json), "playlists.json", "application/json");
|
this.download(JSON.stringify(json), "playlists.json", "application/json");
|
||||||
},
|
},
|
||||||
async fetchPlaylistJson(playlistId) {
|
async fetchPlaylistJson(playlistId) {
|
||||||
let playlist = await this.fetchJson(this.authApiUrl() + "/playlists/" + playlistId);
|
let playlist = await this.getPlaylist(playlistId);
|
||||||
let playlistJson = {
|
return {
|
||||||
name: playlist.name,
|
name: playlist.name,
|
||||||
// possible other types: history, watch later, ...
|
// possible other types: history, watch later, ...
|
||||||
type: "playlist",
|
type: "playlist",
|
||||||
@ -233,7 +187,6 @@ export default {
|
|||||||
// list of the videos, starting with "https://youtube.com" to clarify that those are YT videos
|
// list of the videos, starting with "https://youtube.com" to clarify that those are YT videos
|
||||||
videos: playlist.relatedStreams.map(stream => "https://youtube.com" + stream.url),
|
videos: playlist.relatedStreams.map(stream => "https://youtube.com" + stream.url),
|
||||||
};
|
};
|
||||||
return playlistJson;
|
|
||||||
},
|
},
|
||||||
async importPlaylists() {
|
async importPlaylists() {
|
||||||
const files = this.$refs.fileSelector.files;
|
const files = this.$refs.fileSelector.files;
|
||||||
@ -252,8 +205,8 @@ export default {
|
|||||||
alert(this.$t("actions.no_valid_playlists"));
|
alert(this.$t("actions.no_valid_playlists"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (var i = 0; i < playlists.length; i++) {
|
for (let playlist of playlists) {
|
||||||
tasks.push(this.createPlaylistWithVideos(playlists[i]));
|
tasks.push(this.createPlaylistWithVideos(playlist));
|
||||||
}
|
}
|
||||||
// CSV from Google Takeout
|
// CSV from Google Takeout
|
||||||
} else if (file.name.slice(-4).toLowerCase() == ".csv") {
|
} else if (file.name.slice(-4).toLowerCase() == ".csv") {
|
||||||
@ -277,19 +230,6 @@ export default {
|
|||||||
let videoIds = playlist.videos.map(url => url.substr(-11));
|
let videoIds = playlist.videos.map(url => url.substr(-11));
|
||||||
await this.addVideosToPlaylist(newPlaylist.playlistId, videoIds);
|
await this.addVideosToPlaylist(newPlaylist.playlistId, videoIds);
|
||||||
},
|
},
|
||||||
async addVideosToPlaylist(playlistId, videoIds) {
|
|
||||||
await this.fetchJson(this.authApiUrl() + "/user/playlists/add", null, {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({
|
|
||||||
playlistId: playlistId,
|
|
||||||
videoIds: videoIds,
|
|
||||||
}),
|
|
||||||
headers: {
|
|
||||||
Authorization: this.getAuthToken(),
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
async loadPlaylistBookmarks() {
|
async loadPlaylistBookmarks() {
|
||||||
if (!window.db) return;
|
if (!window.db) return;
|
||||||
var tx = window.db.transaction("playlist_bookmarks", "readonly");
|
var tx = window.db.transaction("playlist_bookmarks", "readonly");
|
||||||
@ -298,8 +238,7 @@ export default {
|
|||||||
cursorRequest.onsuccess = e => {
|
cursorRequest.onsuccess = e => {
|
||||||
const cursor = e.target.result;
|
const cursor = e.target.result;
|
||||||
if (cursor) {
|
if (cursor) {
|
||||||
const bookmark = cursor.value;
|
this.bookmarks.push(cursor.value);
|
||||||
this.bookmarks.push(bookmark);
|
|
||||||
cursor.continue();
|
cursor.continue();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -107,7 +107,7 @@
|
|||||||
>
|
>
|
||||||
<font-awesome-icon icon="headphones" />
|
<font-awesome-icon icon="headphones" />
|
||||||
</router-link>
|
</router-link>
|
||||||
<button v-if="authenticated" :title="$t('actions.add_to_playlist')" @click="showModal = !showModal">
|
<button :title="$t('actions.add_to_playlist')" @click="showModal = !showModal">
|
||||||
<font-awesome-icon icon="circle-plus" />
|
<font-awesome-icon icon="circle-plus" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@ -124,7 +124,12 @@
|
|||||||
@confirm="removeVideo(item.url.substr(-11))"
|
@confirm="removeVideo(item.url.substr(-11))"
|
||||||
:message="$t('actions.delete_playlist_video_confirm')"
|
:message="$t('actions.delete_playlist_video_confirm')"
|
||||||
/>
|
/>
|
||||||
<PlaylistAddModal v-if="showModal" :video-id="item.url.substr(-11)" @close="showModal = !showModal" />
|
<PlaylistAddModal
|
||||||
|
v-if="showModal"
|
||||||
|
:video-id="item.url.substr(-11)"
|
||||||
|
:video-info="item"
|
||||||
|
@close="showModal = !showModal"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -172,17 +177,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
removeVideo() {
|
removeVideo() {
|
||||||
this.$refs.removeButton.disabled = true;
|
this.$refs.removeButton.disabled = true;
|
||||||
this.fetchJson(this.authApiUrl() + "/user/playlists/remove", null, {
|
this.removeVideoFromPlaylist(this.playlistId, this.index).then(json => {
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({
|
|
||||||
playlistId: this.playlistId,
|
|
||||||
index: this.index,
|
|
||||||
}),
|
|
||||||
headers: {
|
|
||||||
Authorization: this.getAuthToken(),
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
}).then(json => {
|
|
||||||
if (json.error) alert(json.error);
|
if (json.error) alert(json.error);
|
||||||
else this.$emit("remove");
|
else this.$emit("remove");
|
||||||
});
|
});
|
||||||
|
@ -78,7 +78,12 @@
|
|||||||
<!-- Verified Badge -->
|
<!-- Verified Badge -->
|
||||||
<font-awesome-icon class="ml-1" v-if="video.uploaderVerified" icon="check" />
|
<font-awesome-icon class="ml-1" v-if="video.uploaderVerified" icon="check" />
|
||||||
</div>
|
</div>
|
||||||
<PlaylistAddModal v-if="showModal" :video-id="getVideoId()" @close="showModal = !showModal" />
|
<PlaylistAddModal
|
||||||
|
v-if="showModal"
|
||||||
|
:video-id="getVideoId()"
|
||||||
|
:video-info="video"
|
||||||
|
@close="showModal = !showModal"
|
||||||
|
/>
|
||||||
<ShareModal
|
<ShareModal
|
||||||
v-if="showShareModal"
|
v-if="showShareModal"
|
||||||
:video-id="getVideoId()"
|
:video-id="getVideoId()"
|
||||||
@ -89,7 +94,7 @@
|
|||||||
/>
|
/>
|
||||||
<div class="flex flex-wrap gap-1 ml-auto">
|
<div class="flex flex-wrap gap-1 ml-auto">
|
||||||
<!-- Subscribe Button -->
|
<!-- Subscribe Button -->
|
||||||
<button class="btn flex items-center" v-if="authenticated" @click="showModal = !showModal">
|
<button class="btn flex items-center" @click="showModal = !showModal">
|
||||||
{{ $t("actions.add_to_playlist") }}<font-awesome-icon class="ml-1" icon="circle-plus" />
|
{{ $t("actions.add_to_playlist") }}<font-awesome-icon class="ml-1" icon="circle-plus" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
239
src/main.js
239
src/main.js
@ -293,6 +293,245 @@ const mixin = {
|
|||||||
var store = tx.objectStore("channel_groups");
|
var store = tx.objectStore("channel_groups");
|
||||||
store.delete(groupName);
|
store.delete(groupName);
|
||||||
},
|
},
|
||||||
|
async getLocalPlaylist(playlistId) {
|
||||||
|
return await new Promise(resolve => {
|
||||||
|
var tx = window.db.transaction("playlists", "readonly");
|
||||||
|
var store = tx.objectStore("playlists");
|
||||||
|
const req = store.openCursor(playlistId);
|
||||||
|
let playlist = null;
|
||||||
|
req.onsuccess = e => {
|
||||||
|
playlist = e.target.result.value;
|
||||||
|
playlist.videos = JSON.parse(playlist.videoIds).length;
|
||||||
|
resolve(playlist);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
createOrUpdateLocalPlaylist(playlist) {
|
||||||
|
var tx = window.db.transaction("playlists", "readwrite");
|
||||||
|
var store = tx.objectStore("playlists");
|
||||||
|
store.put(playlist);
|
||||||
|
},
|
||||||
|
// needs to handle both, streamInfo items and streams items
|
||||||
|
createLocalPlaylistVideo(videoId, videoInfo) {
|
||||||
|
if (videoInfo === undefined || videoId === null || videoInfo?.error) return;
|
||||||
|
|
||||||
|
var tx = window.db.transaction("playlist_videos", "readwrite");
|
||||||
|
var store = tx.objectStore("playlist_videos");
|
||||||
|
const video = {
|
||||||
|
videoId: videoId,
|
||||||
|
title: videoInfo.title,
|
||||||
|
type: "stream",
|
||||||
|
shortDescription: videoInfo.shortDescription ?? videoInfo.description,
|
||||||
|
url: `/watch?v=${videoId}`,
|
||||||
|
thumbnail: videoInfo.thumbnail ?? videoInfo.thumbnailUrl,
|
||||||
|
uploaderVerified: videoInfo.uploaderVerified,
|
||||||
|
duration: videoInfo.duration,
|
||||||
|
uploaderAvatar: videoInfo.uploaderAvatar,
|
||||||
|
uploaderUrl: videoInfo.uploaderUrl,
|
||||||
|
uploaderName: videoInfo.uploaderName ?? videoInfo.uploader,
|
||||||
|
};
|
||||||
|
store.put(video);
|
||||||
|
},
|
||||||
|
async getLocalPlaylistVideo(videoId) {
|
||||||
|
return await new Promise(resolve => {
|
||||||
|
var tx = window.db.transaction("playlist_videos", "readonly");
|
||||||
|
var store = tx.objectStore("playlist_videos");
|
||||||
|
const req = store.openCursor(videoId);
|
||||||
|
req.onsuccess = e => {
|
||||||
|
resolve(e.target.result.value);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async getPlaylists() {
|
||||||
|
if (!this.authenticated) {
|
||||||
|
if (!window.db) return [];
|
||||||
|
return await new Promise(resolve => {
|
||||||
|
let playlists = [];
|
||||||
|
var tx = window.db.transaction("playlists", "readonly");
|
||||||
|
var store = tx.objectStore("playlists");
|
||||||
|
const cursorRequest = store.openCursor();
|
||||||
|
cursorRequest.onsuccess = e => {
|
||||||
|
const cursor = e.target.result;
|
||||||
|
if (cursor) {
|
||||||
|
let playlist = cursor.value;
|
||||||
|
playlist.videos = JSON.parse(playlist.videoIds).length;
|
||||||
|
playlists.push(playlist);
|
||||||
|
cursor.continue();
|
||||||
|
} else {
|
||||||
|
resolve(playlists);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.fetchJson(this.authApiUrl() + "/user/playlists", null, {
|
||||||
|
headers: {
|
||||||
|
Authorization: this.getAuthToken(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async getPlaylist(playlistId) {
|
||||||
|
if (!this.authenticated) {
|
||||||
|
const playlist = await this.getLocalPlaylist(playlistId);
|
||||||
|
const videoIds = JSON.parse(playlist.videoIds);
|
||||||
|
const videosFuture = videoIds.map(videoId => this.getLocalPlaylistVideo(videoId));
|
||||||
|
playlist.relatedStreams = await Promise.all(videosFuture);
|
||||||
|
return playlist;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.fetchJson(this.authApiUrl() + "/playlists/" + playlistId);
|
||||||
|
},
|
||||||
|
async createPlaylist(name) {
|
||||||
|
if (!this.authenticated) {
|
||||||
|
const uuid = crypto.randomUUID();
|
||||||
|
const playlistId = `local-${uuid}`;
|
||||||
|
this.createOrUpdateLocalPlaylist({
|
||||||
|
playlistId: playlistId,
|
||||||
|
// remapping needed for the playlists page
|
||||||
|
id: playlistId,
|
||||||
|
name: name,
|
||||||
|
description: "",
|
||||||
|
thumbnail: "https://pipedproxy.kavin.rocks/?host=i.ytimg.com",
|
||||||
|
videoIds: "[]", // empty list
|
||||||
|
});
|
||||||
|
return { playlistId: playlistId };
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.fetchJson(this.authApiUrl() + "/user/playlists/create", null, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: name,
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
Authorization: this.getAuthToken(),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async deletePlaylist(playlistId) {
|
||||||
|
if (!this.authenticated) {
|
||||||
|
const playlist = await this.getLocalPlaylist(playlistId);
|
||||||
|
var tx = window.db.transaction("playlists", "readwrite");
|
||||||
|
var store = tx.objectStore("playlists");
|
||||||
|
store.delete(playlistId);
|
||||||
|
// delete videos that don't need to be store anymore
|
||||||
|
const playlists = await this.getPlaylists();
|
||||||
|
const usedVideoIds = playlists
|
||||||
|
.filter(playlist => playlist.id != playlistId)
|
||||||
|
.map(playlist => JSON.parse(playlist.videoIds))
|
||||||
|
.flat();
|
||||||
|
const potentialDeletableVideos = JSON.parse(playlist.videoIds);
|
||||||
|
var videoTx = window.db.transaction("playlist_videos", "readwrite");
|
||||||
|
var videoStore = videoTx.objectStore("playlist_videos");
|
||||||
|
for (let videoId of potentialDeletableVideos) {
|
||||||
|
if (!usedVideoIds.includes(videoId)) videoStore.delete(videoId);
|
||||||
|
}
|
||||||
|
return { message: "ok" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.fetchJson(this.authApiUrl() + "/user/playlists/delete", null, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
playlistId: playlistId,
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
Authorization: this.getAuthToken(),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async renamePlaylist(playlistId, newName) {
|
||||||
|
if (!this.authenticated) {
|
||||||
|
const playlist = await this.getLocalPlaylist(playlistId);
|
||||||
|
playlist.name = newName;
|
||||||
|
this.createOrUpdateLocalPlaylist(playlist);
|
||||||
|
return { message: "ok" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.fetchJson(this.authApiUrl() + "/user/playlists/rename", null, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
playlistId: playlistId,
|
||||||
|
newName: newName,
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
Authorization: this.getAuthToken(),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async changePlaylistDescription(playlistId, newDescription) {
|
||||||
|
if (!this.authenticated) {
|
||||||
|
const playlist = await this.getLocalPlaylist(playlistId);
|
||||||
|
playlist.description = newDescription;
|
||||||
|
this.createOrUpdateLocalPlaylist(playlist);
|
||||||
|
return { message: "ok" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.fetchJson(this.authApiUrl() + "/user/playlists/description", null, {
|
||||||
|
method: "PATCH",
|
||||||
|
body: JSON.stringify({
|
||||||
|
playlistId: playlistId,
|
||||||
|
description: newDescription,
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
Authorization: this.getAuthToken(),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async addVideosToPlaylist(playlistId, videoIds, videoInfos) {
|
||||||
|
if (!this.authenticated) {
|
||||||
|
const playlist = await this.getLocalPlaylist(playlistId);
|
||||||
|
const currentVideoIds = JSON.parse(playlist.videoIds);
|
||||||
|
if (currentVideoIds.length == 0) playlist.thumbnail = videoInfos[0].thumbnail;
|
||||||
|
currentVideoIds.push(...videoIds);
|
||||||
|
playlist.videoIds = JSON.stringify(currentVideoIds);
|
||||||
|
this.createOrUpdateLocalPlaylist(playlist);
|
||||||
|
let streamInfos =
|
||||||
|
videoInfos ??
|
||||||
|
(await Promise.all(videoIds.map(videoId => this.fetchJson(this.apiUrl() + "/streams/" + videoId))));
|
||||||
|
for (let i in videoIds) {
|
||||||
|
this.createLocalPlaylistVideo(videoIds[i], streamInfos[i]);
|
||||||
|
}
|
||||||
|
return { message: "ok" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.fetchJson(this.authApiUrl() + "/user/playlists/add", null, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
playlistId: playlistId,
|
||||||
|
videoIds: videoIds,
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
Authorization: this.getAuthToken(),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async removeVideoFromPlaylist(playlistId, index) {
|
||||||
|
if (!this.authenticated) {
|
||||||
|
const playlist = await this.getLocalPlaylist(playlistId);
|
||||||
|
const videoIds = JSON.parse(playlist.videoIds);
|
||||||
|
videoIds.splice(index, 1);
|
||||||
|
playlist.videoIds = JSON.stringify(videoIds);
|
||||||
|
if (videoIds.length == 0) playlist.thumbnail = "https://pipedproxy.kavin.rocks/?host=i.ytimg.com";
|
||||||
|
this.createOrUpdateLocalPlaylist(playlist);
|
||||||
|
return { message: "ok" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.fetchJson(this.authApiUrl() + "/user/playlists/remove", null, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
playlistId: playlistId,
|
||||||
|
index: index,
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
Authorization: this.getAuthToken(),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
authenticated(_this) {
|
authenticated(_this) {
|
||||||
|
Loading…
Reference in New Issue
Block a user