mirror of
https://github.com/TeamPiped/Piped.git
synced 2025-01-10 23:07:00 +00:00
Add support for local playlists
This commit is contained in:
parent
2e64c4f003
commit
37d6423e08
@ -55,7 +55,7 @@ export default {
|
||||
});
|
||||
|
||||
if ("indexedDB" in window) {
|
||||
const request = indexedDB.open("piped-db", 4);
|
||||
const request = indexedDB.open("piped-db", 5);
|
||||
request.onupgradeneeded = ev => {
|
||||
const db = request.result;
|
||||
console.log("Upgrading object store.");
|
||||
@ -77,6 +77,12 @@ export default {
|
||||
const store = db.createObjectStore("channel_groups", { keyPath: "groupName" });
|
||||
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("playlistVideos", { keyPath: "videoId" });
|
||||
playlistVideosStore.createIndex("videoId", "videoId", { unique: true });
|
||||
}
|
||||
};
|
||||
request.onsuccess = e => {
|
||||
window.db = e.target.result;
|
||||
|
@ -86,14 +86,11 @@ export default {
|
||||
mounted() {
|
||||
const playlistId = this.$route.query.list;
|
||||
if (this.authenticated && playlistId?.length == 36)
|
||||
this.fetchJson(this.authApiUrl() + "/user/playlists", null, {
|
||||
headers: {
|
||||
Authorization: this.getAuthToken(),
|
||||
},
|
||||
}).then(json => {
|
||||
this.getPlaylists().then(json => {
|
||||
if (json.error) alert(json.error);
|
||||
else if (json.some(playlist => playlist.id === playlistId)) this.admin = true;
|
||||
});
|
||||
else if (playlistId.startsWith("local")) this.admin = true;
|
||||
this.isPlaylistBookmarked();
|
||||
},
|
||||
activated() {
|
||||
@ -106,6 +103,11 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
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);
|
||||
},
|
||||
async getPlaylistData() {
|
||||
|
@ -238,8 +238,7 @@ export default {
|
||||
cursorRequest.onsuccess = e => {
|
||||
const cursor = e.target.result;
|
||||
if (cursor) {
|
||||
const bookmark = cursor.value;
|
||||
this.bookmarks.push(bookmark);
|
||||
this.bookmarks.push(cursor.value);
|
||||
cursor.continue();
|
||||
}
|
||||
};
|
||||
|
@ -107,7 +107,7 @@
|
||||
>
|
||||
<font-awesome-icon icon="headphones" />
|
||||
</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" />
|
||||
</button>
|
||||
<button
|
||||
@ -127,7 +127,7 @@
|
||||
<PlaylistAddModal
|
||||
v-if="showModal"
|
||||
:video-id="item.url.substr(-11)"
|
||||
video-info="item"
|
||||
:video-info="item"
|
||||
@close="showModal = !showModal"
|
||||
/>
|
||||
</div>
|
||||
|
@ -94,7 +94,7 @@
|
||||
/>
|
||||
<div class="flex flex-wrap gap-1 ml-auto">
|
||||
<!-- 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" />
|
||||
</button>
|
||||
<button
|
||||
@ -112,7 +112,7 @@
|
||||
title="RSS feed"
|
||||
role="button"
|
||||
v-if="video.uploaderUrl"
|
||||
:href="`${apiUrl()}/feed/unauthenticated/rss?channels=${video.uploaderUrl.split('/')[2]}`"
|
||||
:href="`${apiUrl()}/fss?channels=${video.uploaderUrl.split('/')[2]}`"
|
||||
target="_blank"
|
||||
class="btn flex items-center"
|
||||
>
|
||||
@ -494,7 +494,7 @@ export default {
|
||||
},
|
||||
async fetchSubscribedStatus() {
|
||||
if (!this.channelId) return;
|
||||
if (!this.authenticated) {
|
||||
if ({
|
||||
this.subscribed = this.isSubscribedLocally(this.channelId);
|
||||
return;
|
||||
}
|
||||
@ -531,7 +531,7 @@ export default {
|
||||
});
|
||||
},
|
||||
subscribeHandler() {
|
||||
if (this.authenticated) {
|
||||
if {
|
||||
this.fetchJson(this.authApiUrl() + (this.subscribed ? "/unsubscribe" : "/subscribe"), null, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
|
147
src/main.js
147
src/main.js
@ -194,6 +194,9 @@ const mixin = {
|
||||
timeAgo(time) {
|
||||
return timeAgo.format(time);
|
||||
},
|
||||
async delay(millis) {
|
||||
return await new Promise(r => setTimeout(r, millis));
|
||||
},
|
||||
urlify(string) {
|
||||
if (!string) return "";
|
||||
const urlRegex = /(((https?:\/\/)|(www\.))[^\s]+)/g;
|
||||
@ -292,7 +295,84 @@ const mixin = {
|
||||
var store = tx.objectStore("channel_groups");
|
||||
store.delete(groupName);
|
||||
},
|
||||
async getLocalPlaylist(playlistId) {
|
||||
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;
|
||||
};
|
||||
while (playlist == null) {
|
||||
await this.delay(10);
|
||||
}
|
||||
playlist.videos = JSON.parse(playlist.videoIds).length;
|
||||
return 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 === null || videoId === null) return;
|
||||
|
||||
var tx = window.db.transaction("playlistVideos", "readwrite");
|
||||
var store = tx.objectStore("playlistVideos");
|
||||
const video = {
|
||||
videoId: videoId,
|
||||
title: videoInfo.title,
|
||||
type: "stream",
|
||||
shortDescription: videoInfo.shortDescription ?? videoInfo.description,
|
||||
url: `/watch?v=${videoId}`,
|
||||
thumbnailUrl: 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) {
|
||||
var tx = window.db.transaction("playlistVideos", "readonly");
|
||||
var store = tx.objectStore("playlistVideos");
|
||||
const req = store.openCursor(videoId);
|
||||
let video = null;
|
||||
req.onsuccess = e => {
|
||||
video = e.target.result;
|
||||
};
|
||||
while (video == null) {
|
||||
await this.delay(10);
|
||||
}
|
||||
return video;
|
||||
},
|
||||
async getPlaylists() {
|
||||
if (!this.authenticated) {
|
||||
if (!window.db) return [];
|
||||
let finished = false;
|
||||
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 {
|
||||
finished = true;
|
||||
}
|
||||
};
|
||||
while (!finished) {
|
||||
await this.delay(10);
|
||||
}
|
||||
return playlists;
|
||||
}
|
||||
|
||||
return await this.fetchJson(this.authApiUrl() + "/user/playlists", null, {
|
||||
headers: {
|
||||
Authorization: this.getAuthToken(),
|
||||
@ -300,9 +380,31 @@ const mixin = {
|
||||
});
|
||||
},
|
||||
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 = Promise.all(videosFuture);
|
||||
return playlist;
|
||||
}
|
||||
|
||||
return await this.fetchJson(this.authApiUrl() + "/playlists/" + playlistId);
|
||||
},
|
||||
async createPlaylist(name) {
|
||||
if (!this.authenticated) {
|
||||
const playlistId = "local-1";
|
||||
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({
|
||||
@ -315,6 +417,13 @@ const mixin = {
|
||||
});
|
||||
},
|
||||
async deletePlaylist(playlistId) {
|
||||
if (!this.authenticated) {
|
||||
var tx = window.db.transaction("playlists", "readwrite");
|
||||
var store = tx.objectStore("playlists");
|
||||
store.delete(playlistId);
|
||||
return { message: "ok" };
|
||||
}
|
||||
|
||||
return await this.fetchJson(this.authApiUrl() + "/user/playlists/delete", null, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
@ -327,6 +436,13 @@ const mixin = {
|
||||
});
|
||||
},
|
||||
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({
|
||||
@ -340,6 +456,13 @@ const mixin = {
|
||||
});
|
||||
},
|
||||
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({
|
||||
@ -353,7 +476,19 @@ const mixin = {
|
||||
});
|
||||
},
|
||||
async addVideosToPlaylist(playlistId, videoIds, videoInfos) {
|
||||
if (videoInfos == "hallo") return; //TODO, only needed for local vids
|
||||
if (!this.authenticated) {
|
||||
const playlist = await this.getLocalPlaylist(playlistId);
|
||||
const currentVideoIds = JSON.parse(playlist.videoIds);
|
||||
if (currentVideoIds.length == 0) playlist.thumbnailUrl = videoInfos[0].thumbnailUrl;
|
||||
videoIds.push(...videoIds);
|
||||
playlist.videoIds = JSON.stringify(videoIds);
|
||||
this.createOrUpdateLocalPlaylist(playlist);
|
||||
for (let i in videoIds) {
|
||||
this.createLocalPlaylistVideo(videoIds[i], videoInfos[i]);
|
||||
}
|
||||
return { message: "ok" };
|
||||
}
|
||||
|
||||
return await this.fetchJson(this.authApiUrl() + "/user/playlists/add", null, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
@ -367,6 +502,16 @@ const mixin = {
|
||||
});
|
||||
},
|
||||
async removeVideoFromPlaylist(playlistId, videoId) {
|
||||
if (!this.authenticated) {
|
||||
const playlist = await this.getLocalPlaylist(playlistId);
|
||||
const videoIds = JSON.parse(playlist.videoIds);
|
||||
videoIds.splice(videoIds.indexOf(videoId), 1);
|
||||
playlist.videoIds = JSON.stringify(videoIds);
|
||||
if (videoIds.length == 0) playlist.thumbnailUrl = "";
|
||||
this.createOrUpdateLocalPlaylist(playlist);
|
||||
return { message: "ok" };
|
||||
}
|
||||
|
||||
return await this.fetchJson(this.authApiUrl() + "/user/playlists/add", null, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
|
Loading…
Reference in New Issue
Block a user