mirror of
				https://github.com/TeamPiped/Piped.git
				synced 2024-08-14 23:57:27 +00:00 
			
		
		
		
	Update from Main
This commit is contained in:
		
							parent
							
								
									3444c96118
								
							
						
					
					
						commit
						fa9381a6d7
					
				
					 12 changed files with 266 additions and 75 deletions
				
			
		| 
						 | 
				
			
			@ -36,6 +36,7 @@
 | 
			
		|||
            >
 | 
			
		||||
                <font-awesome-icon icon="rss" />
 | 
			
		||||
            </a>
 | 
			
		||||
            <WatchOnYouTubeButton :link="`https://youtube.com/channel/${this.channel.id}`" />
 | 
			
		||||
            <p>|</p>
 | 
			
		||||
            <button
 | 
			
		||||
                v-for="(tab, index) in tabs"
 | 
			
		||||
| 
						 | 
				
			
			@ -75,11 +76,13 @@
 | 
			
		|||
<script>
 | 
			
		||||
import ErrorHandler from "./ErrorHandler.vue";
 | 
			
		||||
import ContentItem from "./ContentItem.vue";
 | 
			
		||||
import WatchOnYouTubeButton from "./WatchOnYouTubeButton.vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    components: {
 | 
			
		||||
        ErrorHandler,
 | 
			
		||||
        ContentItem,
 | 
			
		||||
        WatchOnYouTubeButton,
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
| 
						 | 
				
			
			@ -201,23 +204,23 @@ export default {
 | 
			
		|||
                    },
 | 
			
		||||
                });
 | 
			
		||||
            } else {
 | 
			
		||||
                this.handleLocalSubscriptions(this.channel.id);
 | 
			
		||||
                if (!this.handleLocalSubscriptions(this.channel.id)) return;
 | 
			
		||||
            }
 | 
			
		||||
            this.subscribed = !this.subscribed;
 | 
			
		||||
        },
 | 
			
		||||
        getTranslatedTabName(tabName) {
 | 
			
		||||
            let translatedTabName = tabName;
 | 
			
		||||
            switch (tabName) {
 | 
			
		||||
                case "Livestreams":
 | 
			
		||||
                case "livestreams":
 | 
			
		||||
                    translatedTabName = this.$t("titles.livestreams");
 | 
			
		||||
                    break;
 | 
			
		||||
                case "Playlists":
 | 
			
		||||
                case "playlists":
 | 
			
		||||
                    translatedTabName = this.$t("titles.playlists");
 | 
			
		||||
                    break;
 | 
			
		||||
                case "Channels":
 | 
			
		||||
                case "channels":
 | 
			
		||||
                    translatedTabName = this.$t("titles.channels");
 | 
			
		||||
                    break;
 | 
			
		||||
                case "Shorts":
 | 
			
		||||
                case "shorts":
 | 
			
		||||
                    translatedTabName = this.$t("video.shorts");
 | 
			
		||||
                    break;
 | 
			
		||||
                default:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@
 | 
			
		|||
            <font-awesome-icon :icon="['fab', 'github']" />
 | 
			
		||||
            <span v-t="'actions.source_code'" />
 | 
			
		||||
        </a>
 | 
			
		||||
        <a href="https://piped-docs.kavin.rocks/" target="_blank">
 | 
			
		||||
        <a href="https://docs.piped.video/" target="_blank">
 | 
			
		||||
            <font-awesome-icon :icon="['fa', 'book']" />
 | 
			
		||||
            <span v-t="'actions.documentation'" />
 | 
			
		||||
        </a>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -159,7 +159,11 @@ export default {
 | 
			
		|||
                : [...new Set((this.getLocalSubscriptions() ?? []).concat(newChannels))];
 | 
			
		||||
            // Sort for better cache hits
 | 
			
		||||
            subscriptions.sort();
 | 
			
		||||
            try {
 | 
			
		||||
                localStorage.setItem("localSubscriptions", JSON.stringify(subscriptions));
 | 
			
		||||
            } catch (e) {
 | 
			
		||||
                alert(this.$t("info.local_storage"));
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,7 @@
 | 
			
		|||
            <div>
 | 
			
		||||
                <strong v-text="`${playlist.videos} ${$t('video.videos')}`" />
 | 
			
		||||
                <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" />
 | 
			
		||||
                </button>
 | 
			
		||||
                <button class="btn mr-1" @click="downloadPlaylistAsTxt">
 | 
			
		||||
| 
						 | 
				
			
			@ -23,6 +23,7 @@
 | 
			
		|||
                <a class="btn" :href="getRssUrl">
 | 
			
		||||
                    <font-awesome-icon icon="rss" />
 | 
			
		||||
                </a>
 | 
			
		||||
                <WatchOnYouTubeButton :link="`https://www.youtube.com/playlist?list=${this.$route.query.list}`" />
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -47,11 +48,13 @@
 | 
			
		|||
<script>
 | 
			
		||||
import ErrorHandler from "./ErrorHandler.vue";
 | 
			
		||||
import VideoItem from "./VideoItem.vue";
 | 
			
		||||
import WatchOnYouTubeButton from "./WatchOnYouTubeButton.vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    components: {
 | 
			
		||||
        ErrorHandler,
 | 
			
		||||
        VideoItem,
 | 
			
		||||
        WatchOnYouTubeButton,
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
| 
						 | 
				
			
			@ -137,7 +140,7 @@ export default {
 | 
			
		|||
        downloadPlaylistAsTxt() {
 | 
			
		||||
            var data = "";
 | 
			
		||||
            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");
 | 
			
		||||
        },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,8 +3,18 @@
 | 
			
		|||
 | 
			
		||||
    <hr />
 | 
			
		||||
 | 
			
		||||
    <div v-if="authenticated">
 | 
			
		||||
        <button v-t="'actions.create_playlist'" class="btn" @click="createPlaylist" />
 | 
			
		||||
    <div>
 | 
			
		||||
        <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 v-for="playlist in playlists" :key="playlist.id" class="efy_trans_filter">
 | 
			
		||||
| 
						 | 
				
			
			@ -36,34 +46,18 @@
 | 
			
		|||
            </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>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import LoginModal from "./LoginModal.vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    components: {
 | 
			
		||||
        LoginModal,
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            playlists: [],
 | 
			
		||||
            showLoginModal: false,
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        if (this.authenticated) this.fetchPlaylists();
 | 
			
		||||
        else this.showLoginModal = true;
 | 
			
		||||
        else this.$router.push("/login");
 | 
			
		||||
    },
 | 
			
		||||
    activated() {
 | 
			
		||||
        document.title = this.$t("titles.playlists") + " - Piped";
 | 
			
		||||
| 
						 | 
				
			
			@ -119,10 +113,16 @@ export default {
 | 
			
		|||
                    else this.playlists = this.playlists.filter(playlist => playlist.id !== id);
 | 
			
		||||
                });
 | 
			
		||||
        },
 | 
			
		||||
        createPlaylist() {
 | 
			
		||||
        onCreatePlaylist() {
 | 
			
		||||
            const name = prompt(this.$t("actions.create_playlist"));
 | 
			
		||||
            if (name)
 | 
			
		||||
                this.fetchJson(this.authApiUrl() + "/user/playlists/create", null, {
 | 
			
		||||
            if (!name) return;
 | 
			
		||||
            this.createPlaylist(name).then(json => {
 | 
			
		||||
                if (json.error) alert(json.error);
 | 
			
		||||
                else this.fetchPlaylists();
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        async createPlaylist(name) {
 | 
			
		||||
            let json = await this.fetchJson(this.authApiUrl() + "/user/playlists/create", null, {
 | 
			
		||||
                method: "POST",
 | 
			
		||||
                body: JSON.stringify({
 | 
			
		||||
                    name: name,
 | 
			
		||||
| 
						 | 
				
			
			@ -131,9 +131,71 @@ export default {
 | 
			
		|||
                    Authorization: this.getAuthToken(),
 | 
			
		||||
                    "Content-Type": "application/json",
 | 
			
		||||
                },
 | 
			
		||||
                }).then(json => {
 | 
			
		||||
                    if (json.error) alert(json.error);
 | 
			
		||||
                    else this.fetchPlaylists();
 | 
			
		||||
            });
 | 
			
		||||
            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)"
 | 
			
		||||
                />
 | 
			
		||||
            </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">
 | 
			
		||||
                <strong v-t="'actions.store_watch_history'" />
 | 
			
		||||
                <input
 | 
			
		||||
| 
						 | 
				
			
			@ -202,6 +232,16 @@
 | 
			
		|||
                    @change="onChange($event)"
 | 
			
		||||
                />
 | 
			
		||||
            </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">
 | 
			
		||||
                <strong v-t="'actions.enabled_codecs'" />
 | 
			
		||||
                <select
 | 
			
		||||
| 
						 | 
				
			
			@ -419,6 +459,8 @@ export default {
 | 
			
		|||
            minimizeComments: false,
 | 
			
		||||
            minimizeDescription: false,
 | 
			
		||||
            minimizeRecommendations: false,
 | 
			
		||||
            minimizeChapters: false,
 | 
			
		||||
            showWatchOnYouTube: false,
 | 
			
		||||
            watchHistory: false,
 | 
			
		||||
            searchHistory: false,
 | 
			
		||||
            hideWatched: false,
 | 
			
		||||
| 
						 | 
				
			
			@ -444,6 +486,7 @@ export default {
 | 
			
		|||
                { code: "hi", name: "हिंदी" },
 | 
			
		||||
                { code: "id", name: "Indonesia" },
 | 
			
		||||
                { code: "is", name: "Íslenska" },
 | 
			
		||||
                { code: "kab", name: "Taqbaylit" },
 | 
			
		||||
                { code: "hr", name: "Hrvatski" },
 | 
			
		||||
                { code: "it", name: "Italiano" },
 | 
			
		||||
                { code: "ja", name: "日本語" },
 | 
			
		||||
| 
						 | 
				
			
			@ -452,10 +495,12 @@ export default {
 | 
			
		|||
                { code: "ml", name: "മലയാളം" },
 | 
			
		||||
                { code: "nb_NO", name: "Norwegian Bokmål" },
 | 
			
		||||
                { code: "nl", name: "Nederlands" },
 | 
			
		||||
                { code: "or", name: "ଓଡ଼ିଆ" },
 | 
			
		||||
                { code: "pl", name: "Polski" },
 | 
			
		||||
                { code: "pt", name: "Português" },
 | 
			
		||||
                { code: "pt_PT", name: "Português (Portugal)" },
 | 
			
		||||
                { code: "pt_BR", name: "Português (Brasil)" },
 | 
			
		||||
                { code: "ro", name: "Română" },
 | 
			
		||||
                { code: "ru", name: "Русский" },
 | 
			
		||||
                { code: "sr", name: "Српски" },
 | 
			
		||||
                { code: "sv", name: "Svenska" },
 | 
			
		||||
| 
						 | 
				
			
			@ -555,9 +600,11 @@ export default {
 | 
			
		|||
            this.minimizeComments = this.getPreferenceBoolean("minimizeComments", false);
 | 
			
		||||
            this.minimizeDescription = this.getPreferenceBoolean("minimizeDescription", 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.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.disableLBRY = this.getPreferenceBoolean("disableLBRY", false);
 | 
			
		||||
            this.proxyLBRY = this.getPreferenceBoolean("proxyLBRY", false);
 | 
			
		||||
| 
						 | 
				
			
			@ -581,8 +628,8 @@ export default {
 | 
			
		|||
                if (
 | 
			
		||||
                    this.getPreferenceString("theme", "dark") !== this.selectedTheme ||
 | 
			
		||||
                    this.getPreferenceBoolean("watchHistory", false) != this.watchHistory ||
 | 
			
		||||
                    this.getPreferenceString("hl", await this.defaultLangage) !== this.selectedLanguage ||
 | 
			
		||||
                    this.getPreferenceString("enabledCodecs", "av1,vp9,avc") !== this.enabledCodecs.join(",")
 | 
			
		||||
                    this.getPreferenceString("hl", await this.defaultLanguage) !== this.selectedLanguage ||
 | 
			
		||||
                    this.getPreferenceString("enabledCodecs", "vp9,avc") !== this.enabledCodecs.join(",")
 | 
			
		||||
                )
 | 
			
		||||
                    shouldReload = true;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -614,6 +661,8 @@ export default {
 | 
			
		|||
                localStorage.setItem("minimizeComments", this.minimizeComments);
 | 
			
		||||
                localStorage.setItem("minimizeDescription", this.minimizeDescription);
 | 
			
		||||
                localStorage.setItem("minimizeRecommendations", this.minimizeRecommendations);
 | 
			
		||||
                localStorage.setItem("minimizeChapters", this.minimizeChapters);
 | 
			
		||||
                localStorage.setItem("showWatchOnYouTube", this.showWatchOnYouTube);
 | 
			
		||||
                localStorage.setItem("watchHistory", this.watchHistory);
 | 
			
		||||
                localStorage.setItem("searchHistory", this.searchHistory);
 | 
			
		||||
                if (!this.searchHistory) localStorage.removeItem("search_history");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -66,8 +66,8 @@ export default {
 | 
			
		|||
            }
 | 
			
		||||
        },
 | 
			
		||||
        onChange() {
 | 
			
		||||
            this.setPreference("shareWithTimeCode", this.withTimeCode);
 | 
			
		||||
            this.setPreference("shareAsPipedLink", this.pipedLink);
 | 
			
		||||
            this.setPreference("shareWithTimeCode", this.withTimeCode, true);
 | 
			
		||||
            this.setPreference("shareAsPipedLink", this.pipedLink, true);
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,27 +9,38 @@
 | 
			
		|||
        <i18n-t keypath="subscriptions.subscribed_channels_count">{{ subscriptions.length }}</i18n-t>
 | 
			
		||||
    </div>
 | 
			
		||||
    <hr />
 | 
			
		||||
 | 
			
		||||
    <div class="grid">
 | 
			
		||||
        <div class="mb-3" v-for="subscription in subscriptions" :key="subscription.url">
 | 
			
		||||
            <div class="flex justify-center place-items-center">
 | 
			
		||||
                <div class="w-full flex justify-between items-center">
 | 
			
		||||
    <!-- Subscriptions card list -->
 | 
			
		||||
    <div class="pp-subs-cards">
 | 
			
		||||
        <!-- channel info card -->
 | 
			
		||||
        <div efy_card class="pp-subs-card" v-for="subscription in subscriptions" :key="subscription.url">
 | 
			
		||||
            <router-link :to="subscription.url" class="pp-import-channel flex font-bold">
 | 
			
		||||
                <img :src="subscription.avatar" width="48" height="48" />
 | 
			
		||||
                <span class="mx-2" v-text="subscription.name" />
 | 
			
		||||
            </router-link>
 | 
			
		||||
            <!-- (un)subscribe btn -->
 | 
			
		||||
            <button
 | 
			
		||||
                        class="btn w-min"
 | 
			
		||||
                @click="handleButton(subscription)"
 | 
			
		||||
                v-t="`actions.${subscription.subscribed ? 'unsubscribe' : 'subscribe'}`"
 | 
			
		||||
            />
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <br />
 | 
			
		||||
</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>
 | 
			
		||||
export default {
 | 
			
		||||
    data() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,6 +17,7 @@ export default {
 | 
			
		|||
        };
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        if (this.$route.path == "/" && this.getPreferenceString("homepage", "trending") == "feed") return;
 | 
			
		||||
        let region = this.getPreferenceString("region", "US");
 | 
			
		||||
 | 
			
		||||
        this.fetchTrending(region).then(videos => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -273,7 +273,11 @@ export default {
 | 
			
		|||
                    ).generate_dash_file_from_formats(streams, this.video.duration);
 | 
			
		||||
 | 
			
		||||
                    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";
 | 
			
		||||
            } else if (lbry) {
 | 
			
		||||
                uri = lbry.url;
 | 
			
		||||
| 
						 | 
				
			
			@ -372,13 +376,13 @@ export default {
 | 
			
		|||
                });
 | 
			
		||||
 | 
			
		||||
                videoEl.addEventListener("volumechange", () => {
 | 
			
		||||
                    this.setPreference("volume", videoEl.volume);
 | 
			
		||||
                    this.setPreference("volume", videoEl.volume, true);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                videoEl.addEventListener("ratechange", e => {
 | 
			
		||||
                    const rate = videoEl.playbackRate;
 | 
			
		||||
                    if (rate > 0 && !isNaN(videoEl.duration) && !isNaN(videoEl.duration - e.timeStamp / 1000))
 | 
			
		||||
                        this.setPreference("rate", rate);
 | 
			
		||||
                        this.setPreference("rate", rate, true);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                videoEl.addEventListener("ended", () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -440,7 +444,14 @@ export default {
 | 
			
		|||
 | 
			
		||||
                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) {
 | 
			
		||||
                    overflowMenuButtons.push("open_new_tab");
 | 
			
		||||
| 
						 | 
				
			
			@ -480,22 +491,40 @@ export default {
 | 
			
		|||
            if (qualityConds) this.$player.configure("abr.enabled", false);
 | 
			
		||||
 | 
			
		||||
            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) {
 | 
			
		||||
                    var leastDiff = Number.MAX_VALUE;
 | 
			
		||||
                    var bestStream = null;
 | 
			
		||||
 | 
			
		||||
                    var bestAudio = 0;
 | 
			
		||||
 | 
			
		||||
                    const tracks = player
 | 
			
		||||
                        .getVariantTracks()
 | 
			
		||||
                        .filter(track => track.language == lang || track.language == "und");
 | 
			
		||||
 | 
			
		||||
                    // Choose the best audio stream
 | 
			
		||||
                    if (quality >= 480)
 | 
			
		||||
                        player.getVariantTracks().forEach(track => {
 | 
			
		||||
                        tracks.forEach(track => {
 | 
			
		||||
                            const audioBandwidth = track.audioBandwidth;
 | 
			
		||||
                            if (audioBandwidth > bestAudio) bestAudio = audioBandwidth;
 | 
			
		||||
                        });
 | 
			
		||||
 | 
			
		||||
                    // Find best matching stream based on resolution and bitrate
 | 
			
		||||
                    player
 | 
			
		||||
                        .getVariantTracks()
 | 
			
		||||
                    tracks
 | 
			
		||||
                        .sort((a, b) => a.bandwidth - b.bandwidth)
 | 
			
		||||
                        .forEach(stream => {
 | 
			
		||||
                            if (stream.audioBandwidth < bestAudio) return;
 | 
			
		||||
| 
						 | 
				
			
			@ -706,6 +735,7 @@ html .shaka-range-element:focus {
 | 
			
		|||
.material-icons-round,
 | 
			
		||||
.shaka-current-time {
 | 
			
		||||
    -webkit-text-fill-color: #fff !important;
 | 
			
		||||
    filter: none !important;
 | 
			
		||||
    opacity: 1 !important;
 | 
			
		||||
}
 | 
			
		||||
.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" />
 | 
			
		||||
                    </a>
 | 
			
		||||
                    <!-- watch on youtube button -->
 | 
			
		||||
                    <WatchOnYouTubeButton :link="`https://youtu.be/${getVideoId()}`" />
 | 
			
		||||
                    <!-- Share Dialog -->
 | 
			
		||||
                    <button class="btn" @click="showShareModal = !showShareModal">
 | 
			
		||||
                        <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" />
 | 
			
		||||
| 
						 | 
				
			
			@ -212,6 +213,7 @@ import ChaptersBar from "./ChaptersBar.vue";
 | 
			
		|||
import PlaylistAddModal from "./PlaylistAddModal.vue";
 | 
			
		||||
import ShareModal from "./ShareModal.vue";
 | 
			
		||||
import PlaylistVideos from "./PlaylistVideos.vue";
 | 
			
		||||
import WatchOnYouTubeButton from "./WatchOnYouTubeButton.vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    name: "App",
 | 
			
		||||
| 
						 | 
				
			
			@ -224,6 +226,7 @@ export default {
 | 
			
		|||
        PlaylistAddModal,
 | 
			
		||||
        ShareModal,
 | 
			
		||||
        PlaylistVideos,
 | 
			
		||||
        WatchOnYouTubeButton,
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        const smallViewQuery = window.matchMedia("(max-width: 640px)");
 | 
			
		||||
| 
						 | 
				
			
			@ -330,6 +333,7 @@ export default {
 | 
			
		|||
        this.showComments = !this.getPreferenceBoolean("minimizeComments", false);
 | 
			
		||||
        this.showDesc = !this.getPreferenceBoolean("minimizeDescription", false);
 | 
			
		||||
        this.showRecs = !this.getPreferenceBoolean("minimizeRecommendations", false);
 | 
			
		||||
        this.showChapters = !this.getPreferenceBoolean("minimizeChapters", false);
 | 
			
		||||
        if (this.video.duration) {
 | 
			
		||||
            document.title = this.video.title + " - Piped";
 | 
			
		||||
            this.$refs.videoPlayer.loadVideo();
 | 
			
		||||
| 
						 | 
				
			
			@ -368,7 +372,7 @@ export default {
 | 
			
		|||
            return this.fetchJson(this.apiUrl() + "/comments/" + this.getVideoId());
 | 
			
		||||
        },
 | 
			
		||||
        onChange() {
 | 
			
		||||
            this.setPreference("autoplay", this.selectedAutoPlay);
 | 
			
		||||
            this.setPreference("autoplay", this.selectedAutoPlay, true);
 | 
			
		||||
        },
 | 
			
		||||
        async getVideoData() {
 | 
			
		||||
            await this.fetchVideo()
 | 
			
		||||
| 
						 | 
				
			
			@ -470,7 +474,7 @@ export default {
 | 
			
		|||
                    },
 | 
			
		||||
                });
 | 
			
		||||
            } else {
 | 
			
		||||
                this.handleLocalSubscriptions(this.channelId);
 | 
			
		||||
                if (!this.handleLocalSubscriptions(this.channelId)) return;
 | 
			
		||||
            }
 | 
			
		||||
            this.subscribed = !this.subscribed;
 | 
			
		||||
        },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue