mirror of
https://github.com/TeamPiped/Piped.git
synced 2024-11-15 12:48:22 +00:00
Update from Main
This commit is contained in:
parent
3444c96118
commit
fa9381a6d7
@ -36,6 +36,7 @@
|
|||||||
>
|
>
|
||||||
<font-awesome-icon icon="rss" />
|
<font-awesome-icon icon="rss" />
|
||||||
</a>
|
</a>
|
||||||
|
<WatchOnYouTubeButton :link="`https://youtube.com/channel/${this.channel.id}`" />
|
||||||
<p>|</p>
|
<p>|</p>
|
||||||
<button
|
<button
|
||||||
v-for="(tab, index) in tabs"
|
v-for="(tab, index) in tabs"
|
||||||
@ -75,11 +76,13 @@
|
|||||||
<script>
|
<script>
|
||||||
import ErrorHandler from "./ErrorHandler.vue";
|
import ErrorHandler from "./ErrorHandler.vue";
|
||||||
import ContentItem from "./ContentItem.vue";
|
import ContentItem from "./ContentItem.vue";
|
||||||
|
import WatchOnYouTubeButton from "./WatchOnYouTubeButton.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
ErrorHandler,
|
ErrorHandler,
|
||||||
ContentItem,
|
ContentItem,
|
||||||
|
WatchOnYouTubeButton,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -201,23 +204,23 @@ export default {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.handleLocalSubscriptions(this.channel.id);
|
if (!this.handleLocalSubscriptions(this.channel.id)) return;
|
||||||
}
|
}
|
||||||
this.subscribed = !this.subscribed;
|
this.subscribed = !this.subscribed;
|
||||||
},
|
},
|
||||||
getTranslatedTabName(tabName) {
|
getTranslatedTabName(tabName) {
|
||||||
let translatedTabName = tabName;
|
let translatedTabName = tabName;
|
||||||
switch (tabName) {
|
switch (tabName) {
|
||||||
case "Livestreams":
|
case "livestreams":
|
||||||
translatedTabName = this.$t("titles.livestreams");
|
translatedTabName = this.$t("titles.livestreams");
|
||||||
break;
|
break;
|
||||||
case "Playlists":
|
case "playlists":
|
||||||
translatedTabName = this.$t("titles.playlists");
|
translatedTabName = this.$t("titles.playlists");
|
||||||
break;
|
break;
|
||||||
case "Channels":
|
case "channels":
|
||||||
translatedTabName = this.$t("titles.channels");
|
translatedTabName = this.$t("titles.channels");
|
||||||
break;
|
break;
|
||||||
case "Shorts":
|
case "shorts":
|
||||||
translatedTabName = this.$t("video.shorts");
|
translatedTabName = this.$t("video.shorts");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<font-awesome-icon :icon="['fab', 'github']" />
|
<font-awesome-icon :icon="['fab', 'github']" />
|
||||||
<span v-t="'actions.source_code'" />
|
<span v-t="'actions.source_code'" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://piped-docs.kavin.rocks/" target="_blank">
|
<a href="https://docs.piped.video/" target="_blank">
|
||||||
<font-awesome-icon :icon="['fa', 'book']" />
|
<font-awesome-icon :icon="['fa', 'book']" />
|
||||||
<span v-t="'actions.documentation'" />
|
<span v-t="'actions.documentation'" />
|
||||||
</a>
|
</a>
|
||||||
|
@ -159,7 +159,11 @@ export default {
|
|||||||
: [...new Set((this.getLocalSubscriptions() ?? []).concat(newChannels))];
|
: [...new Set((this.getLocalSubscriptions() ?? []).concat(newChannels))];
|
||||||
// Sort for better cache hits
|
// Sort for better cache hits
|
||||||
subscriptions.sort();
|
subscriptions.sort();
|
||||||
localStorage.setItem("localSubscriptions", JSON.stringify(subscriptions));
|
try {
|
||||||
|
localStorage.setItem("localSubscriptions", JSON.stringify(subscriptions));
|
||||||
|
} catch (e) {
|
||||||
|
alert(this.$t("info.local_storage"));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<strong v-text="`${playlist.videos} ${$t('video.videos')}`" />
|
<strong v-text="`${playlist.videos} ${$t('video.videos')}`" />
|
||||||
<br />
|
<br />
|
||||||
<button class="btn mr-1" v-if="authenticated && !isPipedPlaylist" @click="clonePlaylist">
|
<button class="btn mr-1 ml-2" v-if="authenticated && !isPipedPlaylist" @click="clonePlaylist">
|
||||||
{{ $t("actions.clone_playlist") }}<font-awesome-icon class="ml-3" icon="clone" />
|
{{ $t("actions.clone_playlist") }}<font-awesome-icon class="ml-3" icon="clone" />
|
||||||
</button>
|
</button>
|
||||||
<button class="btn mr-1" @click="downloadPlaylistAsTxt">
|
<button class="btn mr-1" @click="downloadPlaylistAsTxt">
|
||||||
@ -23,6 +23,7 @@
|
|||||||
<a class="btn" :href="getRssUrl">
|
<a class="btn" :href="getRssUrl">
|
||||||
<font-awesome-icon icon="rss" />
|
<font-awesome-icon icon="rss" />
|
||||||
</a>
|
</a>
|
||||||
|
<WatchOnYouTubeButton :link="`https://www.youtube.com/playlist?list=${this.$route.query.list}`" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -47,11 +48,13 @@
|
|||||||
<script>
|
<script>
|
||||||
import ErrorHandler from "./ErrorHandler.vue";
|
import ErrorHandler from "./ErrorHandler.vue";
|
||||||
import VideoItem from "./VideoItem.vue";
|
import VideoItem from "./VideoItem.vue";
|
||||||
|
import WatchOnYouTubeButton from "./WatchOnYouTubeButton.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
ErrorHandler,
|
ErrorHandler,
|
||||||
VideoItem,
|
VideoItem,
|
||||||
|
WatchOnYouTubeButton,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -137,7 +140,7 @@ export default {
|
|||||||
downloadPlaylistAsTxt() {
|
downloadPlaylistAsTxt() {
|
||||||
var data = "";
|
var data = "";
|
||||||
this.playlist.relatedStreams.forEach(element => {
|
this.playlist.relatedStreams.forEach(element => {
|
||||||
data += "https://piped.kavin.rocks" + element.url + "\n";
|
data += "https://piped.video" + element.url + "\n";
|
||||||
});
|
});
|
||||||
this.download(data, this.playlist.name + ".txt", "text/plain");
|
this.download(data, this.playlist.name + ".txt", "text/plain");
|
||||||
},
|
},
|
||||||
|
@ -3,8 +3,18 @@
|
|||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<div v-if="authenticated">
|
<div>
|
||||||
<button v-t="'actions.create_playlist'" class="btn" @click="createPlaylist" />
|
<div class="flex">
|
||||||
|
<button v-t="'actions.create_playlist'" class="btn mr-2" @click="onCreatePlaylist" />
|
||||||
|
<button
|
||||||
|
v-if="this.playlists.length > 0"
|
||||||
|
v-t="'actions.export_to_json'"
|
||||||
|
class="btn"
|
||||||
|
@click="exportPlaylists"
|
||||||
|
/>
|
||||||
|
<input id="fileSelector" ref="fileSelector" type="file" class="display-none" @change="importPlaylists" />
|
||||||
|
<label for="fileSelector" v-t="'actions.import_from_json'" class="btn ml-2" role="button" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="video-grid">
|
<div class="video-grid">
|
||||||
<div v-for="playlist in playlists" :key="playlist.id" class="efy_trans_filter">
|
<div v-for="playlist in playlists" :key="playlist.id" class="efy_trans_filter">
|
||||||
@ -36,34 +46,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="text-center h-[65vh] flex flex-col justify-center items-center">
|
|
||||||
<h1 v-t="'actions.not_logged_in'"></h1>
|
|
||||||
<div class="flex mt-100 items-center children:(mx-30)">
|
|
||||||
<button @click="showLoginModal = true" v-t="'titles.account'"></button>
|
|
||||||
<a class="btn h-min!" href="/" v-t="'actions.back_to_home'"></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<LoginModal v-if="showLoginModal" @close="showLoginModal = !showLoginModal" />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import LoginModal from "./LoginModal.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
|
||||||
LoginModal,
|
|
||||||
},
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
playlists: [],
|
playlists: [],
|
||||||
showLoginModal: false,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.authenticated) this.fetchPlaylists();
|
if (this.authenticated) this.fetchPlaylists();
|
||||||
else this.showLoginModal = true;
|
else this.$router.push("/login");
|
||||||
},
|
},
|
||||||
activated() {
|
activated() {
|
||||||
document.title = this.$t("titles.playlists") + " - Piped";
|
document.title = this.$t("titles.playlists") + " - Piped";
|
||||||
@ -119,22 +113,90 @@ export default {
|
|||||||
else this.playlists = this.playlists.filter(playlist => playlist.id !== id);
|
else this.playlists = this.playlists.filter(playlist => playlist.id !== id);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
createPlaylist() {
|
onCreatePlaylist() {
|
||||||
const name = prompt(this.$t("actions.create_playlist"));
|
const name = prompt(this.$t("actions.create_playlist"));
|
||||||
if (name)
|
if (!name) return;
|
||||||
this.fetchJson(this.authApiUrl() + "/user/playlists/create", null, {
|
this.createPlaylist(name).then(json => {
|
||||||
method: "POST",
|
if (json.error) alert(json.error);
|
||||||
body: JSON.stringify({
|
else this.fetchPlaylists();
|
||||||
name: name,
|
});
|
||||||
}),
|
},
|
||||||
headers: {
|
async createPlaylist(name) {
|
||||||
Authorization: this.getAuthToken(),
|
let json = await this.fetchJson(this.authApiUrl() + "/user/playlists/create", null, {
|
||||||
"Content-Type": "application/json",
|
method: "POST",
|
||||||
},
|
body: JSON.stringify({
|
||||||
}).then(json => {
|
name: name,
|
||||||
if (json.error) alert(json.error);
|
}),
|
||||||
else this.fetchPlaylists();
|
headers: {
|
||||||
});
|
Authorization: this.getAuthToken(),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return json;
|
||||||
|
},
|
||||||
|
async exportPlaylists() {
|
||||||
|
if (!this.playlists) return;
|
||||||
|
let json = {
|
||||||
|
format: "Piped",
|
||||||
|
version: 1,
|
||||||
|
playlists: [],
|
||||||
|
};
|
||||||
|
let tasks = [];
|
||||||
|
for (var i = 0; i < this.playlists.length; i++) {
|
||||||
|
tasks.push(this.fetchPlaylistJson(this.playlists[i].id));
|
||||||
|
}
|
||||||
|
json.playlists = await Promise.all(tasks);
|
||||||
|
this.download(JSON.stringify(json), "playlists.json", "application/json");
|
||||||
|
},
|
||||||
|
async fetchPlaylistJson(playlistId) {
|
||||||
|
let playlist = await this.fetchJson(this.authApiUrl() + "/playlists/" + playlistId);
|
||||||
|
let playlistJson = {
|
||||||
|
name: playlist.name,
|
||||||
|
// possible other types: history, watch later, ...
|
||||||
|
type: "playlist",
|
||||||
|
// as Invidious supports public and private playlists
|
||||||
|
visibility: "private",
|
||||||
|
// list of the videos, starting with "https://youtube.com" to clarify that those are YT videos
|
||||||
|
videos: [],
|
||||||
|
};
|
||||||
|
for (var i = 0; i < playlist.relatedStreams.length; i++) {
|
||||||
|
playlistJson.videos.push("https://youtube.com" + playlist.relatedStreams[i].url);
|
||||||
|
}
|
||||||
|
return playlistJson;
|
||||||
|
},
|
||||||
|
async importPlaylists() {
|
||||||
|
const file = this.$refs.fileSelector.files[0];
|
||||||
|
let text = await file.text();
|
||||||
|
let playlists = JSON.parse(text).playlists;
|
||||||
|
if (!playlists.length) {
|
||||||
|
alert(this.$t("actions.no_valid_playlists"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let tasks = [];
|
||||||
|
for (var i = 0; i < playlists.length; i++) {
|
||||||
|
tasks.push(this.createPlaylistWithVideos(playlists[i]));
|
||||||
|
}
|
||||||
|
await Promise.all(tasks);
|
||||||
|
window.location.reload();
|
||||||
|
},
|
||||||
|
async createPlaylistWithVideos(playlist) {
|
||||||
|
let newPlaylist = await this.createPlaylist(playlist.name);
|
||||||
|
console.log(newPlaylist);
|
||||||
|
let videoIds = playlist.videos.map(url => url.substr(-11));
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -192,6 +192,36 @@
|
|||||||
@change="onChange($event)"
|
@change="onChange($event)"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="pref" for="chkMinimizeChapters">
|
||||||
|
<strong v-t="'actions.minimize_chapters_default'" />
|
||||||
|
<input
|
||||||
|
id="chkMinimizeChapters"
|
||||||
|
v-model="minimizeChapters"
|
||||||
|
class="checkbox"
|
||||||
|
type="checkbox"
|
||||||
|
@change="onChange($event)"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class="pref" for="chkShowWatchOnYouTube">
|
||||||
|
<strong v-t="'actions.show_watch_on_youtube'" />
|
||||||
|
<input
|
||||||
|
id="chkShowWatchOnYouTube"
|
||||||
|
v-model="showWatchOnYouTube"
|
||||||
|
class="checkbox"
|
||||||
|
type="checkbox"
|
||||||
|
@change="onChange($event)"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class="pref" for="chkStoreSearchHistory">
|
||||||
|
<strong v-t="'actions.store_search_history'" />
|
||||||
|
<input
|
||||||
|
id="chkStoreSearchHistory"
|
||||||
|
v-model="searchHistory"
|
||||||
|
class="checkbox"
|
||||||
|
type="checkbox"
|
||||||
|
@change="onChange($event)"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
<label class="pref" for="chkStoreWatchHistory">
|
<label class="pref" for="chkStoreWatchHistory">
|
||||||
<strong v-t="'actions.store_watch_history'" />
|
<strong v-t="'actions.store_watch_history'" />
|
||||||
<input
|
<input
|
||||||
@ -202,6 +232,16 @@
|
|||||||
@change="onChange($event)"
|
@change="onChange($event)"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
<label v-if="watchHistory" class="pref" for="chkHideWatched">
|
||||||
|
<strong v-t="'actions.hide_watched'" />
|
||||||
|
<input
|
||||||
|
id="chkHideWatched"
|
||||||
|
v-model="hideWatched"
|
||||||
|
class="checkbox"
|
||||||
|
type="checkbox"
|
||||||
|
@change="onChange($event)"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
<label class="pref" for="ddlEnabledCodecs">
|
<label class="pref" for="ddlEnabledCodecs">
|
||||||
<strong v-t="'actions.enabled_codecs'" />
|
<strong v-t="'actions.enabled_codecs'" />
|
||||||
<select
|
<select
|
||||||
@ -419,6 +459,8 @@ export default {
|
|||||||
minimizeComments: false,
|
minimizeComments: false,
|
||||||
minimizeDescription: false,
|
minimizeDescription: false,
|
||||||
minimizeRecommendations: false,
|
minimizeRecommendations: false,
|
||||||
|
minimizeChapters: false,
|
||||||
|
showWatchOnYouTube: false,
|
||||||
watchHistory: false,
|
watchHistory: false,
|
||||||
searchHistory: false,
|
searchHistory: false,
|
||||||
hideWatched: false,
|
hideWatched: false,
|
||||||
@ -444,6 +486,7 @@ export default {
|
|||||||
{ code: "hi", name: "हिंदी" },
|
{ code: "hi", name: "हिंदी" },
|
||||||
{ code: "id", name: "Indonesia" },
|
{ code: "id", name: "Indonesia" },
|
||||||
{ code: "is", name: "Íslenska" },
|
{ code: "is", name: "Íslenska" },
|
||||||
|
{ code: "kab", name: "Taqbaylit" },
|
||||||
{ code: "hr", name: "Hrvatski" },
|
{ code: "hr", name: "Hrvatski" },
|
||||||
{ code: "it", name: "Italiano" },
|
{ code: "it", name: "Italiano" },
|
||||||
{ code: "ja", name: "日本語" },
|
{ code: "ja", name: "日本語" },
|
||||||
@ -452,10 +495,12 @@ export default {
|
|||||||
{ code: "ml", name: "മലയാളം" },
|
{ code: "ml", name: "മലയാളം" },
|
||||||
{ code: "nb_NO", name: "Norwegian Bokmål" },
|
{ code: "nb_NO", name: "Norwegian Bokmål" },
|
||||||
{ code: "nl", name: "Nederlands" },
|
{ code: "nl", name: "Nederlands" },
|
||||||
|
{ code: "or", name: "ଓଡ଼ିଆ" },
|
||||||
{ code: "pl", name: "Polski" },
|
{ code: "pl", name: "Polski" },
|
||||||
{ code: "pt", name: "Português" },
|
{ code: "pt", name: "Português" },
|
||||||
{ code: "pt_PT", name: "Português (Portugal)" },
|
{ code: "pt_PT", name: "Português (Portugal)" },
|
||||||
{ code: "pt_BR", name: "Português (Brasil)" },
|
{ code: "pt_BR", name: "Português (Brasil)" },
|
||||||
|
{ code: "ro", name: "Română" },
|
||||||
{ code: "ru", name: "Русский" },
|
{ code: "ru", name: "Русский" },
|
||||||
{ code: "sr", name: "Српски" },
|
{ code: "sr", name: "Српски" },
|
||||||
{ code: "sv", name: "Svenska" },
|
{ code: "sv", name: "Svenska" },
|
||||||
@ -555,9 +600,11 @@ export default {
|
|||||||
this.minimizeComments = this.getPreferenceBoolean("minimizeComments", false);
|
this.minimizeComments = this.getPreferenceBoolean("minimizeComments", false);
|
||||||
this.minimizeDescription = this.getPreferenceBoolean("minimizeDescription", false);
|
this.minimizeDescription = this.getPreferenceBoolean("minimizeDescription", false);
|
||||||
this.minimizeRecommendations = this.getPreferenceBoolean("minimizeRecommendations", false);
|
this.minimizeRecommendations = this.getPreferenceBoolean("minimizeRecommendations", false);
|
||||||
|
this.minimizeChapters = this.getPreferenceBoolean("minimizeChapters", false);
|
||||||
|
this.showWatchOnYouTube = this.getPreferenceBoolean("showWatchOnYouTube", false);
|
||||||
this.watchHistory = this.getPreferenceBoolean("watchHistory", false);
|
this.watchHistory = this.getPreferenceBoolean("watchHistory", false);
|
||||||
this.searchHistory = this.getPreferenceBoolean("searchHistory", false);
|
this.searchHistory = this.getPreferenceBoolean("searchHistory", false);
|
||||||
this.selectedLanguage = this.getPreferenceString("hl", await this.defaultLangage);
|
this.selectedLanguage = this.getPreferenceString("hl", await this.defaultLanguage);
|
||||||
this.enabledCodecs = this.getPreferenceString("enabledCodecs", "vp9,avc").split(",");
|
this.enabledCodecs = this.getPreferenceString("enabledCodecs", "vp9,avc").split(",");
|
||||||
this.disableLBRY = this.getPreferenceBoolean("disableLBRY", false);
|
this.disableLBRY = this.getPreferenceBoolean("disableLBRY", false);
|
||||||
this.proxyLBRY = this.getPreferenceBoolean("proxyLBRY", false);
|
this.proxyLBRY = this.getPreferenceBoolean("proxyLBRY", false);
|
||||||
@ -581,8 +628,8 @@ export default {
|
|||||||
if (
|
if (
|
||||||
this.getPreferenceString("theme", "dark") !== this.selectedTheme ||
|
this.getPreferenceString("theme", "dark") !== this.selectedTheme ||
|
||||||
this.getPreferenceBoolean("watchHistory", false) != this.watchHistory ||
|
this.getPreferenceBoolean("watchHistory", false) != this.watchHistory ||
|
||||||
this.getPreferenceString("hl", await this.defaultLangage) !== this.selectedLanguage ||
|
this.getPreferenceString("hl", await this.defaultLanguage) !== this.selectedLanguage ||
|
||||||
this.getPreferenceString("enabledCodecs", "av1,vp9,avc") !== this.enabledCodecs.join(",")
|
this.getPreferenceString("enabledCodecs", "vp9,avc") !== this.enabledCodecs.join(",")
|
||||||
)
|
)
|
||||||
shouldReload = true;
|
shouldReload = true;
|
||||||
|
|
||||||
@ -614,6 +661,8 @@ export default {
|
|||||||
localStorage.setItem("minimizeComments", this.minimizeComments);
|
localStorage.setItem("minimizeComments", this.minimizeComments);
|
||||||
localStorage.setItem("minimizeDescription", this.minimizeDescription);
|
localStorage.setItem("minimizeDescription", this.minimizeDescription);
|
||||||
localStorage.setItem("minimizeRecommendations", this.minimizeRecommendations);
|
localStorage.setItem("minimizeRecommendations", this.minimizeRecommendations);
|
||||||
|
localStorage.setItem("minimizeChapters", this.minimizeChapters);
|
||||||
|
localStorage.setItem("showWatchOnYouTube", this.showWatchOnYouTube);
|
||||||
localStorage.setItem("watchHistory", this.watchHistory);
|
localStorage.setItem("watchHistory", this.watchHistory);
|
||||||
localStorage.setItem("searchHistory", this.searchHistory);
|
localStorage.setItem("searchHistory", this.searchHistory);
|
||||||
if (!this.searchHistory) localStorage.removeItem("search_history");
|
if (!this.searchHistory) localStorage.removeItem("search_history");
|
||||||
|
@ -66,8 +66,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onChange() {
|
onChange() {
|
||||||
this.setPreference("shareWithTimeCode", this.withTimeCode);
|
this.setPreference("shareWithTimeCode", this.withTimeCode, true);
|
||||||
this.setPreference("shareAsPipedLink", this.pipedLink);
|
this.setPreference("shareAsPipedLink", this.pipedLink, true);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -9,27 +9,38 @@
|
|||||||
<i18n-t keypath="subscriptions.subscribed_channels_count">{{ subscriptions.length }}</i18n-t>
|
<i18n-t keypath="subscriptions.subscribed_channels_count">{{ subscriptions.length }}</i18n-t>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
|
<!-- Subscriptions card list -->
|
||||||
<div class="grid">
|
<div class="pp-subs-cards">
|
||||||
<div class="mb-3" v-for="subscription in subscriptions" :key="subscription.url">
|
<!-- channel info card -->
|
||||||
<div class="flex justify-center place-items-center">
|
<div efy_card class="pp-subs-card" v-for="subscription in subscriptions" :key="subscription.url">
|
||||||
<div class="w-full flex justify-between items-center">
|
<router-link :to="subscription.url" class="pp-import-channel flex font-bold">
|
||||||
<router-link :to="subscription.url" class="pp-import-channel flex font-bold">
|
<img :src="subscription.avatar" width="48" height="48" />
|
||||||
<img :src="subscription.avatar" width="48" height="48" />
|
<span class="mx-2" v-text="subscription.name" />
|
||||||
<span class="mx-2" v-text="subscription.name" />
|
</router-link>
|
||||||
</router-link>
|
<!-- (un)subscribe btn -->
|
||||||
<button
|
<button
|
||||||
class="btn w-min"
|
@click="handleButton(subscription)"
|
||||||
@click="handleButton(subscription)"
|
v-t="`actions.${subscription.subscribed ? 'unsubscribe' : 'subscribe'}`"
|
||||||
v-t="`actions.${subscription.subscribed ? 'unsubscribe' : 'subscribe'}`"
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.pp-subs-cards {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--efy_gap);
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(240rem, 1fr));
|
||||||
|
}
|
||||||
|
.pp-subs-card :is(a, span) {
|
||||||
|
-webkit-text-fill-color: var(--efy_text) !important;
|
||||||
|
}
|
||||||
|
.pp-subs-card button {
|
||||||
|
margin-bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
|
@ -17,6 +17,7 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
if (this.$route.path == "/" && this.getPreferenceString("homepage", "trending") == "feed") return;
|
||||||
let region = this.getPreferenceString("region", "US");
|
let region = this.getPreferenceString("region", "US");
|
||||||
|
|
||||||
this.fetchTrending(region).then(videos => {
|
this.fetchTrending(region).then(videos => {
|
||||||
|
@ -273,7 +273,11 @@ export default {
|
|||||||
).generate_dash_file_from_formats(streams, this.video.duration);
|
).generate_dash_file_from_formats(streams, this.video.duration);
|
||||||
|
|
||||||
uri = "data:application/dash+xml;charset=utf-8;base64," + btoa(dash);
|
uri = "data:application/dash+xml;charset=utf-8;base64," + btoa(dash);
|
||||||
} else uri = this.video.dash;
|
} else {
|
||||||
|
const url = new URL(this.video.dash);
|
||||||
|
url.searchParams.set("rewrite", false);
|
||||||
|
uri = url.toString();
|
||||||
|
}
|
||||||
mime = "application/dash+xml";
|
mime = "application/dash+xml";
|
||||||
} else if (lbry) {
|
} else if (lbry) {
|
||||||
uri = lbry.url;
|
uri = lbry.url;
|
||||||
@ -372,13 +376,13 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
videoEl.addEventListener("volumechange", () => {
|
videoEl.addEventListener("volumechange", () => {
|
||||||
this.setPreference("volume", videoEl.volume);
|
this.setPreference("volume", videoEl.volume, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
videoEl.addEventListener("ratechange", e => {
|
videoEl.addEventListener("ratechange", e => {
|
||||||
const rate = videoEl.playbackRate;
|
const rate = videoEl.playbackRate;
|
||||||
if (rate > 0 && !isNaN(videoEl.duration) && !isNaN(videoEl.duration - e.timeStamp / 1000))
|
if (rate > 0 && !isNaN(videoEl.duration) && !isNaN(videoEl.duration - e.timeStamp / 1000))
|
||||||
this.setPreference("rate", rate);
|
this.setPreference("rate", rate, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
videoEl.addEventListener("ended", () => {
|
videoEl.addEventListener("ended", () => {
|
||||||
@ -440,7 +444,14 @@ export default {
|
|||||||
|
|
||||||
this.$ui = new shaka.ui.Overlay(localPlayer, this.$refs.container, videoEl);
|
this.$ui = new shaka.ui.Overlay(localPlayer, this.$refs.container, videoEl);
|
||||||
|
|
||||||
const overflowMenuButtons = ["quality", "captions", "picture_in_picture", "playback_rate", "airplay"];
|
const overflowMenuButtons = [
|
||||||
|
"quality",
|
||||||
|
"language",
|
||||||
|
"captions",
|
||||||
|
"picture_in_picture",
|
||||||
|
"playback_rate",
|
||||||
|
"airplay",
|
||||||
|
];
|
||||||
|
|
||||||
if (this.isEmbed) {
|
if (this.isEmbed) {
|
||||||
overflowMenuButtons.push("open_new_tab");
|
overflowMenuButtons.push("open_new_tab");
|
||||||
@ -480,22 +491,40 @@ export default {
|
|||||||
if (qualityConds) this.$player.configure("abr.enabled", false);
|
if (qualityConds) this.$player.configure("abr.enabled", false);
|
||||||
|
|
||||||
player.load(uri, 0, mime).then(() => {
|
player.load(uri, 0, mime).then(() => {
|
||||||
|
const isSafari = window.navigator?.vendor?.includes("Apple");
|
||||||
|
|
||||||
|
if (!isSafari) {
|
||||||
|
// Set the audio language
|
||||||
|
const prefLang = this.getPreferenceString("hl", "en").substr(0, 2);
|
||||||
|
var lang = "en";
|
||||||
|
for (var l in player.getAudioLanguages()) {
|
||||||
|
if (l == prefLang) {
|
||||||
|
lang = l;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
player.selectAudioLanguage(lang);
|
||||||
|
}
|
||||||
|
|
||||||
if (qualityConds) {
|
if (qualityConds) {
|
||||||
var leastDiff = Number.MAX_VALUE;
|
var leastDiff = Number.MAX_VALUE;
|
||||||
var bestStream = null;
|
var bestStream = null;
|
||||||
|
|
||||||
var bestAudio = 0;
|
var bestAudio = 0;
|
||||||
|
|
||||||
|
const tracks = player
|
||||||
|
.getVariantTracks()
|
||||||
|
.filter(track => track.language == lang || track.language == "und");
|
||||||
|
|
||||||
// Choose the best audio stream
|
// Choose the best audio stream
|
||||||
if (quality >= 480)
|
if (quality >= 480)
|
||||||
player.getVariantTracks().forEach(track => {
|
tracks.forEach(track => {
|
||||||
const audioBandwidth = track.audioBandwidth;
|
const audioBandwidth = track.audioBandwidth;
|
||||||
if (audioBandwidth > bestAudio) bestAudio = audioBandwidth;
|
if (audioBandwidth > bestAudio) bestAudio = audioBandwidth;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Find best matching stream based on resolution and bitrate
|
// Find best matching stream based on resolution and bitrate
|
||||||
player
|
tracks
|
||||||
.getVariantTracks()
|
|
||||||
.sort((a, b) => a.bandwidth - b.bandwidth)
|
.sort((a, b) => a.bandwidth - b.bandwidth)
|
||||||
.forEach(stream => {
|
.forEach(stream => {
|
||||||
if (stream.audioBandwidth < bestAudio) return;
|
if (stream.audioBandwidth < bestAudio) return;
|
||||||
@ -706,6 +735,7 @@ html .shaka-range-element:focus {
|
|||||||
.material-icons-round,
|
.material-icons-round,
|
||||||
.shaka-current-time {
|
.shaka-current-time {
|
||||||
-webkit-text-fill-color: #fff !important;
|
-webkit-text-fill-color: #fff !important;
|
||||||
|
filter: none !important;
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
}
|
}
|
||||||
.shaka-overflow-menu,
|
.shaka-overflow-menu,
|
||||||
|
24
src/components/WatchOnYouTubeButton.vue
Normal file
24
src/components/WatchOnYouTubeButton.vue
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
link: String,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<template v-if="this.getPreferenceBoolean('showWatchOnYouTube', false)">
|
||||||
|
<a :href="link" class="pp-watch-onyt-btn btn" role="button" :title="$t('player.watch_on') + 'YouTube'">
|
||||||
|
<font-awesome-icon class="mx-1.5" :icon="['fab', 'youtube']" />
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.pp-watch-onyt-btn {
|
||||||
|
display: flex;
|
||||||
|
margin-left: var(--efy_gap0);
|
||||||
|
align-items: center;
|
||||||
|
gap: 5rem;
|
||||||
|
}
|
||||||
|
</style>
|
@ -96,7 +96,8 @@
|
|||||||
>
|
>
|
||||||
<font-awesome-icon icon="rss" />
|
<font-awesome-icon icon="rss" />
|
||||||
</a>
|
</a>
|
||||||
<!-- watch on youtube button -->
|
<WatchOnYouTubeButton :link="`https://youtu.be/${getVideoId()}`" />
|
||||||
|
<!-- Share Dialog -->
|
||||||
<button class="btn" @click="showShareModal = !showShareModal">
|
<button class="btn" @click="showShareModal = !showShareModal">
|
||||||
<i18n-t class="lt-lg:hidden" keypath="actions.share" tag="strong"></i18n-t>
|
<i18n-t class="lt-lg:hidden" keypath="actions.share" tag="strong"></i18n-t>
|
||||||
<font-awesome-icon class="mx-1.5 ml-1" icon="fa-share" />
|
<font-awesome-icon class="mx-1.5 ml-1" icon="fa-share" />
|
||||||
@ -212,6 +213,7 @@ import ChaptersBar from "./ChaptersBar.vue";
|
|||||||
import PlaylistAddModal from "./PlaylistAddModal.vue";
|
import PlaylistAddModal from "./PlaylistAddModal.vue";
|
||||||
import ShareModal from "./ShareModal.vue";
|
import ShareModal from "./ShareModal.vue";
|
||||||
import PlaylistVideos from "./PlaylistVideos.vue";
|
import PlaylistVideos from "./PlaylistVideos.vue";
|
||||||
|
import WatchOnYouTubeButton from "./WatchOnYouTubeButton.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "App",
|
name: "App",
|
||||||
@ -224,6 +226,7 @@ export default {
|
|||||||
PlaylistAddModal,
|
PlaylistAddModal,
|
||||||
ShareModal,
|
ShareModal,
|
||||||
PlaylistVideos,
|
PlaylistVideos,
|
||||||
|
WatchOnYouTubeButton,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
const smallViewQuery = window.matchMedia("(max-width: 640px)");
|
const smallViewQuery = window.matchMedia("(max-width: 640px)");
|
||||||
@ -330,6 +333,7 @@ export default {
|
|||||||
this.showComments = !this.getPreferenceBoolean("minimizeComments", false);
|
this.showComments = !this.getPreferenceBoolean("minimizeComments", false);
|
||||||
this.showDesc = !this.getPreferenceBoolean("minimizeDescription", false);
|
this.showDesc = !this.getPreferenceBoolean("minimizeDescription", false);
|
||||||
this.showRecs = !this.getPreferenceBoolean("minimizeRecommendations", false);
|
this.showRecs = !this.getPreferenceBoolean("minimizeRecommendations", false);
|
||||||
|
this.showChapters = !this.getPreferenceBoolean("minimizeChapters", false);
|
||||||
if (this.video.duration) {
|
if (this.video.duration) {
|
||||||
document.title = this.video.title + " - Piped";
|
document.title = this.video.title + " - Piped";
|
||||||
this.$refs.videoPlayer.loadVideo();
|
this.$refs.videoPlayer.loadVideo();
|
||||||
@ -368,7 +372,7 @@ export default {
|
|||||||
return this.fetchJson(this.apiUrl() + "/comments/" + this.getVideoId());
|
return this.fetchJson(this.apiUrl() + "/comments/" + this.getVideoId());
|
||||||
},
|
},
|
||||||
onChange() {
|
onChange() {
|
||||||
this.setPreference("autoplay", this.selectedAutoPlay);
|
this.setPreference("autoplay", this.selectedAutoPlay, true);
|
||||||
},
|
},
|
||||||
async getVideoData() {
|
async getVideoData() {
|
||||||
await this.fetchVideo()
|
await this.fetchVideo()
|
||||||
@ -470,7 +474,7 @@ export default {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.handleLocalSubscriptions(this.channelId);
|
if (!this.handleLocalSubscriptions(this.channelId)) return;
|
||||||
}
|
}
|
||||||
this.subscribed = !this.subscribed;
|
this.subscribed = !this.subscribed;
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user