0
0
Fork 0
mirror of https://github.com/VueTubeApp/VueTube synced 2024-11-25 12:45:17 +00:00
This commit is contained in:
Kenny 2022-06-14 20:10:46 -04:00
commit e239726048
60 changed files with 1066 additions and 521 deletions

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: 💬 Discord
url: https://vuetube.app/discord
about: Join the Discord server to chat and ask questions
- name: 💬 Telegram
url: https://t.me/vuetube
about: Join the Telegram group to chat and ask questions

37
.github/ISSUE_TEMPLATE/question.yml vendored Normal file
View file

@ -0,0 +1,37 @@
name: ❓ Question
description: Ask a quesion related to VueTube
labels: [question]
body:
- type: textarea
id: question
attributes:
label: Ask your question
description: What do you want to know?
placeholder: |
Example:
"How do I add a plugin?"
validations:
required: true
- type: textarea
id: aditional-info
attributes:
label: Aditional information
placeholder: |
Additional useful information, for example, a screenshot.
- type: checkboxes
id: acknowledgements
attributes:
label: Acknowledgements
description: Your question will be closed if you haven't done these steps.
options:
- label: I have searched the existing issues and this is a new question, **NOT** a duplicate or related to another open issue.
required: true
- label: I have written a short but informative title.
required: true
- label: I will fill out all of the requested information in this form.
required: true
- label: My question isn't asked in FAQ (Frequently Asked Questions).
required: true

View file

@ -106,7 +106,7 @@ jobs:
CODE_SIGNING_ALLOWED="NO"
CODE_SIGN_ENTITLEMENTS=""
- name: Make IPA
run: mkdir Payload && mv ~/Library/Developer/Xcode/DerivedData/App-*/Build/Products/Debug-maccatalyst/App.app/ Payload && zip -r Payload.zip Payload && mv Payload.zip VueTube.ipa
run: mkdir Payload && mv ~/Library/Developer/Xcode/DerivedData/App-*/Build/Products/Debug-iphoneos/App.app/ Payload && zip -r Payload.zip Payload && mv Payload.zip VueTube.ipa
- name: Upload artifacts
uses: actions/upload-artifact@v2

View file

@ -1,14 +0,0 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="512" height="512" fill="url(#paint0_linear_6_11)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M327.768 269.391C338.454 263.236 338.454 247.814 327.766 241.66L327.761 241.658L291.527 220.81L331.399 202.218L345.714 210.453L345.736 210.466L386.392 233.857C403.102 243.471 403.102 267.582 386.392 277.196L345.743 300.582L345.714 300.599L328.778 310.343L288.905 291.75L327.761 269.395L327.768 269.391ZM206 290.605V311.783V311.79C206.005 324.092 219.314 331.788 229.979 325.652L252.89 312.471L292.763 331.063L247.932 356.856L247.903 356.873L207.467 380.137C190.801 389.725 170 377.695 170 358.467L170 311.783L170 273.818L206 290.605ZM295.384 181.497L255.512 200.09L229.979 185.4L229.964 185.392C219.3 179.27 206 186.968 206 199.269V223.177L170 239.965L170 199.269L170 152.585C170 133.357 190.801 121.327 207.467 130.915L247.917 154.187L247.932 154.196L295.384 181.497Z" fill="url(#paint1_linear_6_11)"/>
<defs>
<linearGradient id="paint0_linear_6_11" x1="0" y1="0" x2="512" y2="512" gradientUnits="userSpaceOnUse">
<stop stop-color="#1B3245"/>
<stop offset="1" stop-color="#0C2028"/>
</linearGradient>
<linearGradient id="paint1_linear_6_11" x1="170" y1="128" x2="431" y2="389" gradientUnits="userSpaceOnUse">
<stop stop-color="#00E1C3"/>
<stop offset="1" stop-color="#00D1D5"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1,11 +1,5 @@
<template>
<v-btn
fab
text
small
disabled
style="position: absolute; top: 0.25rem; right: 3rem"
>
<v-btn fab text small disabled>
<v-icon>mdi-closed-caption-outline</v-icon>
</v-btn>
</template>

View file

@ -1,13 +1,6 @@
<template>
<!-- TODO: change /home to $router.goBack() or $router.go(-1) -->
<v-btn
fab
text
small
style="position: absolute; top: 0.25rem; right: 0.25rem"
to="/home"
color="white"
>
<!-- // TODO: change /home to $router.goBack() or $router.go(-1) -->
<v-btn fab text small to="/home" color="white">
<v-icon>mdi-close</v-icon>
</v-btn>
</template>

View file

@ -1,13 +1,8 @@
<template>
<v-btn
fab
text
small
color="white"
style="position: absolute; bottom: 0.25rem; right: 0.25rem"
@click.stop="$emit('fullscreen')"
>
<v-icon>{{ fullscreen ? "mdi-fullscreen-exit" : "mdi-fullscreen" }}</v-icon>
<v-btn fab text small color="white" @click.stop="$emit('fullscreen')">
<v-icon size="1.25rem">{{
fullscreen ? "mdi-fullscreen-exit" : "mdi-fullscreen"
}}</v-icon>
</v-btn>
</template>

View file

@ -8,159 +8,245 @@
}"
class="d-flex flex-column"
style="position: relative"
:style="{
borderRadius: $store.state.tweaks.roundWatch
? `${$store.state.tweaks.roundTweak / 3}rem`
: '0',
}"
@click="controls = !controls"
:style="{ height: isFullscreen ? '100vh' : 'auto' }"
>
<video
ref="player"
autoplay
width="100%"
:height="isFullscreen ? '100%' : 'auto'"
:src="vidSrc"
:height="isFullscreen ? '100%' : 'auto'"
style="transition: filter 0.15s ease-in-out"
:class="controls || seeking ? 'dim' : ''"
:style="contain ? 'object-fit: contain;' : 'object-fit: cover;'"
:class="controls || seeking || skipping ? 'dim' : ''"
:style="{
objectFit: contain ? 'contain' : 'cover',
borderRadius:
$store.state.tweaks.roundWatch && !isFullscreen
? `${$store.state.tweaks.roundTweak / 3}rem ${$store.state.tweaks.roundTweak / 3}rem 0rem 0rem !important`
: '0',
}"
poster="https://media.discordapp.net/attachments/970793575153561640/974728851441729556/bam.png"
@click="controlsHandler()"
@loadedmetadata="checkDimensions()"
/>
<div
v-if="isFullscreen && controls"
style="
position: absolute;
width: calc(100% - 12rem);
left: 3rem;
top: 0.5rem;
"
>
<h4>{{ video.title }}</h4>
<div style="color: #aaa; font-size: 0.75rem">{{ video.channelName }}</div>
</div>
<!-- // NOTE: replace poster URL with "none" -->
<!-- // TODO: merge the bottom 2 into 1 reusable component -->
<v-btn
text
tile
color="white"
:class="skipping == -10 ? '' : 'invisible'"
style="
opacity: 0;
position: absolute;
top: 0;
left: 0;
width: 50%;
height: 100%;
position: absolute;
transition: opacity 0.15s;
border-radius: 0 100vh 100vh 0;
text-transform: none;
font-size: 0.5rem;
"
@dblclick.stop="$refs.player.currentTime -= 10"
@click="controlsHandler()"
@dblclick="skipHandler(-10)"
>
<v-icon>mdi-rewind</v-icon>
<!-- {{ skipping }} seconds -->
</v-btn>
<v-btn
text
tile
color="white"
:class="skipping == 10 ? '' : 'invisible'"
style="
opacity: 0;
position: absolute;
top: 0;
left: 50%;
width: 50%;
height: 100%;
position: absolute;
transition: opacity 0.15s;
border-radius: 100vh 0 0 100vh;
text-transform: none;
font-size: 0.5rem;
"
@dblclick.stop="$refs.player.currentTime += 10"
@click="controlsHandler()"
@dblclick="skipHandler(10)"
>
<v-icon>mdi-fast-forward</v-icon>
<!-- {{ skipping }} seconds] -->
</v-btn>
<div
style="transition: opacity 0.15s ease-in-out"
:style="controls ? 'opacity: 1;' : 'opacity: 0; pointer-events: none'"
v-if="seeking"
class="d-flex justify-center"
style="width: 100%; top: 0.5rem; position: absolute; font-size: 0.66rem"
>
<minimize />
<loop />
<captions />
<close />
<v-btn
fab
text
small
style="
position: absolute;
top: calc(50% - 1.25rem);
left: calc(50% - 10rem);
"
color="white"
@click.stop="$refs.player.currentTime -= 5"
>
<v-icon size="1rem">mdi-rewind-5</v-icon>
</v-btn>
<v-btn
fab
text
style="
position: absolute;
top: calc(50% - 1.75rem);
left: calc(50% - 6.5rem);
"
color="white"
disabled
@click.stop=""
>
<v-icon size="2rem">mdi-skip-previous</v-icon>
</v-btn>
<playpause
v-if="$refs.player"
:video="$refs.player"
@close="controls = false"
/>
<v-btn
fab
text
style="
position: absolute;
top: calc(50% - 1.75rem);
left: calc(50% + 3rem);
"
color="white"
disabled
@click.stop=""
>
<v-icon size="2rem">mdi-skip-next</v-icon>
</v-btn>
<v-btn
fab
text
small
style="
position: absolute;
top: calc(50% - 1.25rem);
left: calc(50% + 7rem);
"
color="white"
@click.stop="$refs.player.currentTime += 5"
>
<v-icon size="1rem">mdi-fast-forward-5</v-icon>
</v-btn>
<watchtime v-if="$refs.player" :video="$refs.player" />
<!-- // TODO: merge the bottom 2 into 1 reusable component -->
<quality v-if="$refs.player" :video="$refs.player" :sources="sources" />
<speed v-if="$refs.player" :video="$refs.player" />
<fullscreen
:fullscreen="isFullscreen"
@fullscreen="(controls = $refs.player.paused), handleFullscreenChange()"
/>
<v-icon small class="pr-2">mdi-rewind</v-icon>
Double tap left or right to skip 10 seconds
<v-icon small class="pl-2">mdi-fast-forward</v-icon>
</div>
<!-- NOTE: breaks in fullscreen -->
<!-- controls container -->
<div
style="transition: opacity 0.15s ease-in-out"
:style="
controls && !seeking
? 'opacity: 1;'
: 'opacity: 0; pointer-events: none'
"
>
<!-- top controls row -->
<div
style="position: absolute; width: 100%; top: 0.25rem"
class="d-flex justify-center px-2"
>
<minimize />
<div v-if="isFullscreen" class="pt-2" @click.self="controlsHandler()">
<h4>{{ video.title }}</h4>
<div style="color: #aaa; font-size: 0.75rem">
{{ video.channelName }}
</div>
</div>
<v-spacer />
<captions />
<loop
v-if="$refs.player"
class="mx-2"
:loop="$refs.player.loop"
@loop="$refs.player.loop = !$refs.player.loop"
/>
<close />
</div>
<!-- top controls row end -->
<!-- center controls row -->
<div
class="d-flex justify-center align-center"
style="
transform: translate(-50%, -50%);
position: absolute;
left: 50%;
top: 50%;
"
>
<v-btn fab text color="white" class="px-8" disabled>
<v-icon size="2rem" color="white">mdi-skip-previous</v-icon>
</v-btn>
<playpause
v-if="$refs.player"
:video="$refs.player"
@play="$refs.player.play()"
@pause="$refs.player.pause()"
/>
<v-btn fab text color="white" class="px-8" disabled>
<v-icon size="2rem" color="white">mdi-skip-next</v-icon>
</v-btn>
</div>
<!-- center controls row end -->
<!-- time & fullscreen row -->
<div
:style="isFullscreen ? 'bottom: 4.25rem' : 'bottom: 0.5rem'"
class="d-flex justify-between align-center pl-4 pr-2"
style="position: absolute; width: 100%"
@click.self="controlsHandler()"
>
<watchtime
v-if="$refs.player"
:current-time="$refs.player.currentTime"
:duration="$refs.player.duration"
/>
<v-spacer />
<fullscreen
style="z-index: 2"
:fullscreen="isFullscreen"
@fullscreen="fullscreenHandler(true)"
/>
</div>
<!-- time & fullscreen row end -->
<!-- bottom controls row -->
<div
style="position: absolute; width: 100%; bottom: 0.5rem"
class="d-flex justify-between align-center px-2"
@click.self="controlsHandler()"
>
<div v-if="isFullscreen">
<v-btn fab text small color="white" class="mr-2" disabled>
<v-icon>mdi-thumb-up-outline</v-icon>
</v-btn>
<v-btn fab text small color="white" class="mr-2" disabled>
<v-icon>mdi-thumb-down-outline</v-icon>
</v-btn>
<v-btn fab text small color="white" class="mr-2" disabled>
<v-icon>mdi-share-outline</v-icon>
</v-btn>
<v-btn fab text small color="white" class="mr-2" disabled>
<v-icon>mdi-plus-box-multiple-outline</v-icon>
</v-btn>
<v-btn fab text small color="white" class="mr-2" disabled>
<v-icon>mdi-comment-text-outline</v-icon>
</v-btn>
</div>
<v-spacer />
<!-- // TODO: merge the bottom 2 into 1 reusable component -->
<quality
v-if="$refs.player"
:sources="sources"
style="z-index: 77777"
:current-source="$refs.player"
@quality="qualityHandler($event)"
/>
<speed
v-if="$refs.player"
class="mx-2"
style="z-index: 77777"
:current-speed="$refs.player.playbackRate"
@speed="$refs.player.playbackRate = $event"
/>
<v-btn v-if="isFullscreen" fab text small disabled @click.stop="">
<v-icon>mdi-cards-outline</v-icon>
</v-btn>
<!-- placeholder for moving fullscreen button above -->
<v-btn v-else fab text small disabled> </v-btn>
</div>
<!-- bottom controls row -->
</div>
<!-- controls container end -->
<progressbar
v-if="$refs.player"
:current-time="$refs.player.currentTime"
:duration="$refs.player.duration"
:fullscreen="isFullscreen"
:controls="controls"
:buffered="buffered"
:seeking="seeking"
/>
<sponsorblock
v-if="$refs.player && blocks.length > 0"
:duration="$refs.player.duration"
:fullscreen="isFullscreen"
:controls="controls"
:seeking="seeking"
:blocks="blocks"
/>
<seekbar
v-if="$refs.player"
v-show="!isFullscreen || controls"
:duration="$refs.player.duration"
:fullscreen="isFullscreen"
:current-time="progress"
:video="$refs.player"
:sources="sources"
:controls="controls"
:sources="sources"
:seeking="seeking"
@seeking="seeking = !seeking"
@scrub="$refs.player.currentTime = $event"
/>
</div>
</template>
@ -176,8 +262,12 @@ import captions from "~/components/Player/captions.vue";
import playpause from "~/components/Player/playpause.vue";
import watchtime from "~/components/Player/watchtime.vue";
import fullscreen from "~/components/Player/fullscreen.vue";
import progressbar from "~/components/Player/progressbar.vue";
import sponsorblock from "~/components/Player/sponsorblock.vue";
export default {
components: {
sponsorblock,
progressbar,
fullscreen,
watchtime,
playpause,
@ -202,66 +292,161 @@ export default {
data() {
return {
isFullscreen: false,
fullscreenLock: false,
verticalFullscreen: false,
midRotation: false,
controls: false,
seeking: false,
contain: true,
skipping: 0,
progress: 0,
buffered: 0,
watched: 0,
blocks: [],
vidSrc: "",
isVerticalVideo: false,
};
},
mounted() {
console.log("sources", this.sources);
this.vidSrc = this.sources[this.sources.length - 1].url;
// TODO: detect orientation change and enter fullscreen
// TODO: detect video loading state and send this.loading to play button :loading = loading
let vid = this.$refs.player;
this.$youtube.getSponsorBlock(this.$route.query.v, (data) => {
sponsorBlock = data.segment;
this.$youtube.getSponsorBlock(this.video.id, (data) => {
console.log("sbreturn", data);
if (Array.isArray(data)) {
this.blocks = data;
}
});
this.$refs.player.ontimeupdate = () => {
let vidTime = this.$refs.player.currentTime;
for (let i = 0; i < sponsorBlock.length; i++) {
if (vidTime > sponsorBlock[i][0] && vidTime < sponsorBlock[0][i]) {
this.$refs.player.currentTime = sponsorBlock[i][0];
break;
}
vid.addEventListener("loadeddata", (e) => {
// TODO: detect video loading state and send this.loading to play button :loading = loading here
// console.log(e);
if (vid.readyState >= 3) {
vid.addEventListener("timeupdate", () => {
if (!this.seeking) this.progress = vid.currentTime; // for seekbar
// console.log("sb check", this.blocks);
// iterate over data.segments array
// for sponsorblock
if (this.blocks.length > 0)
this.blocks.forEach((sponsor) => {
let vidTime = vid.currentTime;
if (
vidTime >= sponsor.segment[0] &&
vidTime <= sponsor.segment[1]
) {
console.log("Skipping the sponsor");
this.$youtube.showToast("Skipped sponsor");
vid.currentTime = sponsor.segment[1] + 1;
}
});
});
// TODO: detect video loading state and send this.loading to play button :loading = loading here
vid.addEventListener("progress", () => {
this.buffered = (vid.buffered.end(0) / vid.duration) * 100;
});
}
}
});
},
created() {
screen.orientation.addEventListener("change", () =>
this.fullscreenHandler(false)
);
},
beforeDestroy() {
if (this.isFullscreen) this.exitFullscreen();
screen.orientation.removeEventListener("change");
},
methods: {
handleFullscreenChange() {
if (document?.fullscreenElement === this.$refs.vidcontainer) {
this.exitFullscreen();
// TODO: make accumulative onclick after first dblclick (don't set timeout untill stopped clicking)
skipHandler(time) {
this.skipping = time;
setTimeout(() => {
this.skipping = false;
}, 500);
this.$refs.player.currentTime += time;
},
controlsHandler() {
if (!this.seeking)
this.controls
? (clearTimeout(this.controls), (this.controls = false))
: setTimeout(() => {
if (!this.skipping) {
this.controls = setTimeout(() => {
if (!this.seeking && !this.$refs.player.paused)
this.controls = false;
}, 2345);
}
}, 250);
},
qualityHandler(q) {
console.log(q);
let time = this.$refs.player.currentTime;
this.$refs.player.src = q;
this.$refs.player.currentTime = time;
},
checkDimensions() {
if (this.$refs.player.videoHeight > this.$refs.player.videoWidth) {
this.isVerticalVideo = true;
} else {
this.openFullscreen();
this.isVerticalVideo = false;
}
},
exitFullscreen() {
const cancellFullScreen =
document.exitFullscreen ||
document.mozCancelFullScreen ||
document.webkitExitFullscreen ||
document.msExitFullscreen;
cancellFullScreen.call(document);
screen.orientation.lock("portrait");
screen.orientation.unlock();
fullscreenHandler(pressedFullscreenBtn) {
// Prevent fullscreen button press from being handled twice
// (once by pressing fullscreen button, another by the resulting rotation)
if (this.midRotation) {
this.midRotation = false;
return;
}
// Toggle fullscreen state
if (this.isFullscreen) {
this.exitFullscreen(pressedFullscreenBtn);
} else {
this.enterFullscreen(pressedFullscreenBtn);
}
},
exitFullscreen(unlock) {
if (unlock) {
if (this.verticalFullscreen) {
// Unset vertical fullscreen mode
screen.orientation.unlock();
this.fullscreenLock = false;
this.verticalFullscreen = false;
} else {
// Unset standard fullscreen mode
this.midRotation = true;
screen.orientation.lock("portrait");
this.fullscreenLock = true;
// Locks the rotation to portrait for two seconds,
// and will then rotate back to landscape if the
// user doesn't rotate first
setTimeout(() => {
this.fullscreenLock = false;
screen.orientation.unlock();
}, 2 * 1000);
}
}
this.$vuetube.navigationBar.show();
this.$vuetube.statusBar.show();
this.isFullscreen = false;
},
openFullscreen() {
const element = this.$refs.vidcontainer;
const requestFullScreen =
element.requestFullscreen ||
element.webkitRequestFullScreen ||
element.mozRequestFullScreen ||
element.msRequestFullScreen;
requestFullScreen.call(element);
screen.orientation.lock("landscape");
enterFullscreen(force) {
if (force) {
this.fullscreenLock = true;
if (this.isVerticalVideo) {
// Vertical fullscreen mode (vertical video only)
screen.orientation.lock("portrait");
this.verticalFullscreen = true;
} else {
// Standard fullscreen mode
this.midRotation = true;
screen.orientation.lock("landscape");
}
}
this.$vuetube.navigationBar.hide();
this.$vuetube.statusBar.hide();
this.isFullscreen = true;
@ -275,7 +460,7 @@ export default {
<style>
.dim {
filter: brightness(42%);
filter: brightness(33%);
}
.invisible {
opacity: 0;

View file

@ -1,11 +1,17 @@
<template>
<v-btn
fab
text
small
disabled
style="position: absolute; top: 0.25rem; right: 6rem"
>
<v-icon>mdi-sync</v-icon>
<v-btn fab text small @click="$emit('loop')">
<v-icon color="white">{{ loop ? "mdi-sync-circle" : "mdi-sync" }}</v-icon>
</v-btn>
</template>
<script>
export default {
props: {
loop: {
type: Boolean,
default: false,
},
},
emits: ["loop"],
};
</script>

View file

@ -1,11 +1,5 @@
<template>
<v-btn
fab
text
small
disabled
style="position: absolute; top: 0.25rem; left: 0.25rem"
>
<v-btn fab text small disabled>
<v-icon>mdi-chevron-down</v-icon>
</v-btn>
</template>

View file

@ -3,11 +3,9 @@
fab
text
large
style="position: absolute; top: calc(50% - 2rem); left: calc(50% - 2rem)"
color="white"
@click.stop="
(paused = !video.paused),
video.paused ? (video.play(), $emit('close')) : video.pause()
@click="
(paused = !video.paused), video.paused ? $emit('play') : $emit('pause')
"
>
<v-icon size="3.5rem">
@ -18,8 +16,13 @@
<script>
export default {
props: ["video"],
emits: ["close"],
props: {
video: {
type: Object,
required: true,
},
},
emits: ["play", "pause"],
data: () => ({
paused: false,
}),

View file

@ -0,0 +1,51 @@
<template>
<v-progress-linear
style="
position: absolute;
background: #ffffff22;
transform: translateY(50%);
"
background-opacity="0.5"
background-color="white"
:buffer-value="buffered"
:value="(currentTime / duration) * 100"
:class="!fullscreen || controls ? '' : 'invisible'"
color="primary"
:height="seeking ? 4 : 2"
:style="
fullscreen
? 'width: calc(100% - 2rem); left: 1rem; bottom: 3.5rem;'
: 'width: 100%; left: 0; bottom: 1px;'
"
/>
</template>
<script>
export default {
props: {
duration: {
type: Number,
required: true,
},
seeking: {
type: Boolean,
required: true,
},
fullscreen: {
type: Boolean,
required: true,
},
currentTime: {
type: Number,
required: true,
},
controls: {
type: Boolean,
required: true,
},
buffered: {
type: Number,
required: true,
},
},
};
</script>

View file

@ -3,45 +3,47 @@
<v-bottom-sheet
v-model="sheet"
:attach="$parent.$refs.vidcontainer"
style="z-index: 777777"
scrollable
>
<template #activator="{ on, attrs }">
<v-btn
fab
text
small
color="white"
style="position: absolute; bottom: 0.25rem; right: 3rem"
v-bind="attrs"
v-on="on"
>
{{ sources.find((src) => src.url == video.src).qualityLabel }}
<v-btn fab text small color="white" v-bind="attrs" v-on="on">
{{ sources.find((src) => src.url == currentSource.src).qualityLabel }}
</v-btn>
</template>
<v-card
v-touch="{
down: () => (sheet = false),
}"
class="background"
>
<v-subheader>Quality for current video</v-subheader>
<v-card-text style="max-height: 50vh" class="pa-0">
<v-card class="background">
<v-subheader
v-touch="{
down: () => (sheet = false),
}"
>
Quality for current video
<v-spacer />
<v-btn fab text small color="white" @click="sheet = false">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-subheader>
<v-divider />
<v-card-text
style="max-height: 50vh"
class="pa-0 d-flex flex-column-reverse"
>
<v-list-item
v-for="src in sources"
:key="src"
@click="(sheet = false), (video.src = src.url)"
@click="(sheet = false), $emit('quality', src.url)"
>
<v-list-item-avatar>
<v-icon
:color="
video.src === src.url
currentSource.src === src.url
? 'primary'
: $vuetify.theme.dark
? 'background lighten-2'
: 'background darken-2'
"
v-text="
video.src === src.url
currentSource.src === src.url
? 'mdi-radiobox-marked'
: 'mdi-radiobox-blank'
"
@ -59,7 +61,17 @@
<script>
export default {
props: ["video", "sources"],
props: {
currentSource: {
type: String,
required: true,
},
sources: {
type: Array,
required: true,
},
},
emits: ["quality"],
data: () => ({
sheet: false,
}),

View file

@ -7,43 +7,29 @@
style="display: none"
:src="vidWrs"
/>
<v-progress-linear
query
active
style="width: 100%; background: #ffffff22"
background-opacity="0.5"
background-color="white"
:buffer-value="buffered"
:value="percent"
color="primary"
height="3"
:style="
fullscreen
? 'width: calc(100% - 2rem); left: 1rem; position: absolute; bottom: 3rem;'
: 'width: 100%'
"
/>
<!-- Scrubber -->
<v-slider
id="scrubber"
hide-details
height="2"
dense
color="transparent"
thumb-color="primary"
track-color="transparent"
:class="!controls && !fullscreen && !scrubbing ? 'invisible' : ''"
style="position: absolute; z-index: 2"
:class="!controls && !fullscreen && !seeking ? 'invisible' : ''"
style="position: absolute; z-index: 69420"
:style="
fullscreen
? 'width: calc(100% - 2rem); left: 1rem; bottom: 3rem;'
: 'width: calc(100% - 0.8rem); left: 0.4rem; bottom: 0;'
? 'width: calc(100% - 2rem); left: 1rem; bottom: 55px;'
: 'width: calc(100% - 0.5rem); left: 0.25rem; bottom: 0;'
"
:thumb-size="0"
:max="duration"
:value="progress"
@start="(scrubbing = true), $emit('seeking')"
@end="(scrubbing = false), $emit('seeking')"
@change="scrub($event)"
@input="scrubbing ? seek($event) : null"
:value="currentTime"
@start="$emit('seeking')"
@end="$emit('seeking')"
@change="$emit('scrub', $event)"
@input="seeking ? seek($event) : null"
>
<template #thumb-label="{ value }">
<div style="transform: translateY(-50%)">
@ -70,164 +56,44 @@
<script>
export default {
props: ["sources", "video", "controls", "fullscreen"],
data() {
return {
scrubbing: false,
percent: 0,
progress: 0,
buffered: 0,
duration: 0,
vidSrc: "",
vidWrs: "",
};
props: {
video: {
type: Object,
required: true,
},
controls: {
type: Boolean,
required: true,
},
fullscreen: {
type: Boolean,
required: true,
},
sources: {
type: Array,
required: true,
},
currentTime: {
type: Number,
required: true,
},
duration: {
type: Number,
required: true,
},
seeking: {
type: Boolean,
required: true,
},
},
emits: ["scrub", "seeking"],
data: () => ({
vidWrs: "",
}),
mounted() {
console.log("sources", this.sources);
this.vidSrc = this.sources[this.sources.length - 1].url;
this.vidWrs = this.sources[1].url;
let vid = this.video;
vid.addEventListener("loadeddata", (e) => {
// console.log("%c loadeddata", "color: #00ff00");
console.log(e);
//Video should now be loaded but we can add a second check
if (vid.readyState >= 3) {
vid.ontimeupdate = () => {
// console.log("%c timeupdate", "color: #aaaaff");
this.duration = vid.duration;
if (!this.scrubbing) this.progress = vid.currentTime;
this.percent = (vid.currentTime / vid.duration) * 100;
};
vid.onprogress = () => {
// console.log("%c progress", "color: #ff00ff");
this.buffered = (vid.buffered.end(0) / vid.duration) * 100;
};
}
});
},
methods: {
// TODO: better scrubbing preview
loadVideoFrames() {
// Exit loop if desired number of frames have been extracted
if (this.frames.length >= frameCount) {
this.visibleFrame = 0;
// Append all canvases to container div
this.frames.forEach((frame) => {
this.frameContainerElement.appendChild(frame);
});
return;
}
// If extraction hasnt started, set desired time for first frame
if (this.frames.length === 0) {
this.requestedTime = 0;
} else {
this.requestedTime = this.requestedTime + this.frameTimestep;
}
// Send seek request to video player for the next frame.
this.videoElement.currentTime = this.requestedTime;
},
extractFrame(videoWidth, videoHeight) {
// Create DOM canvas object
var canvas = document.createElement("canvas");
canvas.className = "video-scrubber-frame";
canvas.height = videoHeight;
canvas.width = videoWidth;
// Copy current frame to canvas
var context = canvas.getContext("2d");
context.drawImage(this.videoElement, 0, 0, videoWidth, videoHeight);
this.frames.push(canvas);
// Load the next frame
loadVideoFrames();
},
prefetch_file(url, fetched_callback, progress_callback, error_callback) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.responseType = "blob";
xhr.addEventListener(
"load",
function () {
if (xhr.status === 200) {
var URL = window.URL || window.webkitURL;
var blob_url = URL.createObjectURL(xhr.response);
fetched_callback(blob_url);
} else {
error_callback();
}
},
false
);
var prev_pc = 0;
xhr.addEventListener("progress", function (event) {
if (event.lengthComputable) {
var pc = Math.round((event.loaded / event.total) * 100);
if (pc != prev_pc) {
prev_pc = pc;
progress_callback(pc);
}
}
});
xhr.send();
},
async extractFramesFromVideo(videoUrl, fps = 25) {
// fully download it first (no buffering):
console.log(videoUrl);
console.log(fps);
let videoBlob = await fetch(videoUrl, {
headers: { range: "bytes=0-567139" },
}).then((r) => r.blob());
console.log(videoBlob);
let videoObjectUrl = URL.createObjectURL(videoBlob);
let video = document.createElement("video");
let seekResolve;
video.addEventListener("seeked", async function () {
if (seekResolve) seekResolve();
});
video.src = videoObjectUrl;
// workaround chromium metadata bug (https://stackoverflow.com/q/38062864/993683)
while (
(video.duration === Infinity || isNaN(video.duration)) &&
video.readyState < 2
) {
await new Promise((r) => setTimeout(r, 1000));
video.currentTime = 10000000 * Math.random();
}
let duration = video.duration;
let canvas = document.createElement("canvas");
let context = canvas.getContext("2d");
let [w, h] = [video.videoWidth, video.videoHeight];
canvas.width = w;
canvas.height = h;
let interval = 1;
let currentTime = 0;
while (currentTime < duration) {
video.currentTime = currentTime;
await new Promise((r) => (seekResolve = r));
context.drawImage(video, 0, 0, w, h);
let base64ImageData = canvas.toDataURL();
console.log(base64ImageData);
this.frames.push(base64ImageData);
currentTime += interval;
}
console.log("%c frames", "color: #00ff00");
console.log(this.frames);
},
// TODO: scrubbing preview end
seek(e) {
// console.log(`scrubbing ${e}`);
let vid = this.$refs.playerfake;
@ -243,9 +109,127 @@ export default {
this.video.clientHeight / 3
);
},
scrub(e) {
this.video.currentTime = e;
},
// TODO: better scrubbing preview (don't delet ples 🙏)
// loadVideoFrames() {
// // Exit loop if desired number of frames have been extracted
// if (this.frames.length >= frameCount) {
// this.visibleFrame = 0;
// // Append all canvases to container div
// this.frames.forEach((frame) => {
// this.frameContainerElement.appendChild(frame);
// });
// return;
// }
// // If extraction hasnt started, set desired time for first frame
// if (this.frames.length === 0) {
// this.requestedTime = 0;
// } else {
// this.requestedTime = this.requestedTime + this.frameTimestep;
// }
// // Send seek request to video player for the next frame.
// this.videoElement.currentTime = this.requestedTime;
// },
// extractFrame(videoWidth, videoHeight) {
// // Create DOM canvas object
// var canvas = document.createElement("canvas");
// canvas.className = "video-scrubber-frame";
// canvas.height = videoHeight;
// canvas.width = videoWidth;
// // Copy current frame to canvas
// var context = canvas.getContext("2d");
// context.drawImage(this.videoElement, 0, 0, videoWidth, videoHeight);
// this.frames.push(canvas);
// // Load the next frame
// loadVideoFrames();
// },
// prefetch_file(url, fetched_callback, progress_callback, error_callback) {
// var xhr = new XMLHttpRequest();
// xhr.open("GET", url, true);
// xhr.responseType = "blob";
// xhr.addEventListener(
// "load",
// function () {
// if (xhr.status === 200) {
// var URL = window.URL || window.webkitURL;
// var blob_url = URL.createObjectURL(xhr.response);
// fetched_callback(blob_url);
// } else {
// error_callback();
// }
// },
// false
// );
// var prev_pc = 0;
// xhr.addEventListener("progress", function (event) {
// if (event.lengthComputable) {
// var pc = Math.round((event.loaded / event.total) * 100);
// if (pc != prev_pc) {
// prev_pc = pc;
// progress_callback(pc);
// }
// }
// });
// xhr.send();
// },
// async extractFramesFromVideo(videoUrl, fps = 25) {
// // fully download it first (no buffering):
// console.log(videoUrl);
// console.log(fps);
// let videoBlob = await fetch(videoUrl, {
// headers: { range: "bytes=0-567139" },
// }).then((r) => r.blob());
// console.log(videoBlob);
// let videoObjectUrl = URL.createObjectURL(videoBlob);
// let video = document.createElement("video");
// let seekResolve;
// video.addEventListener("seeked", async function () {
// if (seekResolve) seekResolve();
// });
// video.src = videoObjectUrl;
// // workaround chromium metadata bug (https://stackoverflow.com/q/38062864/993683)
// while (
// (video.duration === Infinity || isNaN(video.duration)) &&
// video.readyState < 2
// ) {
// await new Promise((r) => setTimeout(r, 1000));
// video.currentTime = 10000000 * Math.random();
// }
// let duration = video.duration;
// let canvas = document.createElement("canvas");
// let context = canvas.getContext("2d");
// let [w, h] = [video.videoWidth, video.videoHeight];
// canvas.width = w;
// canvas.height = h;
// let interval = 1;
// let currentTime = 0;
// while (currentTime < duration) {
// video.currentTime = currentTime;
// await new Promise((r) => (seekResolve = r));
// context.drawImage(video, 0, 0, w, h);
// let base64ImageData = canvas.toDataURL();
// console.log(base64ImageData);
// this.frames.push(base64ImageData);
// currentTime += interval;
// }
// console.log("%c frames", "color: #00ff00");
// console.log(this.frames);
// },
// TODO: scrubbing preview end
},
};
</script>

View file

@ -3,47 +3,44 @@
<v-bottom-sheet
v-model="sheet"
:attach="$parent.$refs.vidcontainer"
style="z-index: 777777"
scrollable
>
<template #activator="{ on, attrs }">
<v-btn
fab
text
small
color="white"
style="position: absolute; bottom: 0.25rem; right: 6rem"
v-bind="attrs"
v-on="on"
>
{{ video.playbackRate }}X
<v-btn fab text small color="white" v-bind="attrs" v-on="on">
{{ currentSpeed }}X
</v-btn>
</template>
<v-card
v-touch="{
down: () => (sheet = false),
}"
class="background"
>
<v-subheader>Playback Speed</v-subheader>
<v-card class="background">
<v-subheader
v-touch="{
down: () => (sheet = false),
}"
>
Playback Speed
<v-spacer />
<v-btn fab text small color="white" @click="sheet = false">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-subheader>
<v-divider />
<v-card-text style="height: 50vh" class="pa-0">
<v-list-item
v-for="sped in speeds"
:key="sped"
@click="(sheet = false), (video.playbackRate = sped)"
@click="(sheet = false), $emit('speed', sped)"
>
<!-- // TODO: save playbackRate to localStorage and manage via store/video/index.js -->
<v-list-item-avatar>
<v-icon
:color="
video.playbackRate === sped
currentSpeed === sped
? 'primary'
: $vuetify.theme.dark
? 'background lighten-2'
: 'background darken-2'
"
v-text="
video.playbackRate === sped ? 'mdi-check' : 'mdi-speedometer'
"
v-text="currentSpeed === sped ? 'mdi-check' : 'mdi-speedometer'"
></v-icon>
</v-list-item-avatar>
<v-list-item-title>{{ sped }}X</v-list-item-title>
@ -56,7 +53,13 @@
<script>
export default {
props: ["video"],
props: {
currentSpeed: {
type: Number,
required: true,
},
},
emits: ["speed"],
data: () => ({
sheet: false,
speeds: [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 3, 4, 8, 16],

View file

@ -0,0 +1,67 @@
<template>
<div>
<v-progress-linear
v-for="block in blocks"
:key="block.UUID"
:buffer-value="(block.segment[1] / duration) * 100"
:value="(block.segment[0] / duration) * 100"
style="
position: absolute;
pointer-events: none;
background: transparent;
transform: translateY(50%);
"
:class="!fullscreen || controls ? '' : 'invisible'"
background-color="white"
background-opacity="1"
color="transparent"
:height="seeking ? 4 : 2"
:style="
fullscreen
? 'width: calc(100% - 2rem); left: 1rem; bottom: 3.5rem;'
: 'width: 100%; left: 0; bottom: 1px;'
"
/>
<!-- // TODO:background-color="colors[block.category]" -->
</div>
</template>
<script>
export default {
props: {
blocks: {
type: Array,
required: true,
},
duration: {
type: Number,
required: true,
},
seeking: {
type: Boolean,
required: true,
},
fullscreen: {
type: Boolean,
required: true,
},
controls: {
type: Boolean,
required: true,
},
},
data: () => ({
colors: {
sponsor: "green",
selfpromo: "yellow",
exclusive_access: "teal",
interaction: "fuchsia",
poi_highlight: "deeppink",
intro: "lightblue",
outro: "blue",
music_offtopic: "orange",
filter: "purple",
},
}),
};
</script>

View file

@ -1,32 +1,21 @@
<template>
<div
style="
color: #fff;
left: 1rem;
bottom: 1rem;
font-size: 0.75rem;
position: absolute;
"
>
{{ watched }}
<span style="color: #aaa"> / {{ duration }} </span>
<div style="color: #fff; font-size: 0.75rem">
{{ $vuetube.humanTime(currentTime) }}
<span style="color: #aaa"> / {{ $vuetube.humanTime(duration) }} </span>
</div>
</template>
<script>
export default {
props: ["video"],
data() {
return {
watched: 0,
duration: 0,
};
},
mounted() {
this.video.addEventListener("timeupdate", () => {
this.duration = this.$vuetube.humanTime(this.video.duration);
this.watched = this.$vuetube.humanTime(this.video.currentTime);
});
props: {
duration: {
type: Number,
required: true,
},
currentTime: {
type: Number,
required: true,
},
},
};
</script>

View file

@ -8,6 +8,7 @@
<style scoped>
.description {
white-space: pre-line;
font-size: 0.9rem;
}
</style>

View file

@ -68,7 +68,7 @@
text
tile
dense
class="searchButton text-left text-none"
class="searchButton text-left text-none no-spacing"
@click="youtubeSearch(item)"
>
<v-icon class="mr-5">mdi-magnify</v-icon>
@ -326,6 +326,10 @@ div {
left: 50%;
transform: translate(-50%, -50%);
}
.no-spacing {
letter-spacing: 0px !important;
}
</style>
<style scoped>

View file

@ -53,6 +53,8 @@ export default {
buildModules: ["@nuxtjs/vuetify"],
modules: [],
ssr: false,
vuetify: {
customVariables: ["~/assets/variables.scss"],
treeShake: true,

View file

@ -6,7 +6,7 @@
<center style="margin: 2em;">
<h1>Registry Editor</h1>
<v-alert text outlined type="warning">
EDITING ENTRIES MAY CAUSE YOUR APP TO BREAK!
CHANGING ENTRIES MAY CAUSE YOUR APP TO BREAK!
</v-alert>
</center>
@ -36,7 +36,6 @@
<v-card class="rounded-lg" :class="$vuetify.theme.dark ? 'background lighten-1' : 'background darken-1'">
<v-card-title class="text-h5">Confirm Delete</v-card-title>
<v-card-text>Are you sure that you want to delete <span class="highlight" v-text="selectedKey" />?</v-card-text>
<v-alert text outlined type="warning" style="margin: -0.5em 2em 1em 2em;">Deleting random keys may cause the app to break!</v-alert>
<v-divider />
<v-card-actions>
<v-spacer></v-spacer>
@ -59,7 +58,6 @@
/>
</v-card-text>
<v-alert text outlined type="warning" style="margin: -2em 2em 1em 2em;">Editing random keys may cause the app to break!</v-alert>
<v-divider />
<v-card-actions>
<v-spacer></v-spacer>

View file

@ -12,7 +12,7 @@
>
<v-btn
text
class="entry text-left text-capitalize"
class="entry text-left text-capitalize no-spacing"
:to="item.to"
:disabled="item.disabled"
:style="{
@ -49,7 +49,7 @@
<v-btn
v-if="devmode"
text
class="entry text-left text-capitalize"
class="entry text-left text-capitalize no-spacing"
to="/mods/developer"
:style="{
borderRadius: `${$store.state.tweaks.roundTweak / 2}rem`,

View file

@ -19,7 +19,3 @@
</p>
</center>
</template>
<script>
export default {};
</script>

View file

@ -21,7 +21,7 @@
<v-card v-if="loaded" class="background rounded-0" flat>
<div
v-ripple
class="d-flex justify-space-between align-start px-3 pt-3"
class="d-flex justify-space-between align-start px-3 pt-4"
@click="showMore = !showMore"
>
<div class="d-flex flex-column">
@ -163,7 +163,7 @@
<!-- Description -->
<div v-if="showMore">
<div class="scroll-y ma-4">
<div class="scroll-y ma-4 pt-1">
<slim-video-description-renderer
:render="video.renderedData.description"
/>
@ -227,6 +227,7 @@
hide-overlay
persistent
no-click-animation
style="z-index: 2 !important"
attach="#content-container"
>
<mainCommentRenderer
@ -366,6 +367,12 @@ export default {
dialogTitle: "Share video",
});
},
vlc() {
this.$youtube.showToast("Opening in VLC");
// redirect to vlc://url
window.location.href = "vlc://" + this.video.availableResolutions[this.video.availableResolutions.length-1].url;
},
sendWatchTime() {
const player = this.$refs.player.getPlayer();
const rt = Math.floor(Date.now() / 1000) - this.startTime;
@ -434,6 +441,12 @@ export default {
actionName: "enqueue",
disabled: true,
},
{
name: "Open in VLC",
icon: "mdi-traffic-cone",
actionName: "vlc",
disabled: false,
},
// {
// name: "Quality",
// icon: "mdi-high-definition",

View file

@ -141,31 +141,11 @@ const module = {
Math.floor((((seconds % 31536000) % 86400) % 3600) % 60), //Seconds
];
levels = levels.filter((level) => level !== null);
for (let i = 1; i < levels.length; i++) {
levels[i] = levels[i].toString().padStart(2, "0");
}
// join the array into a string with : as a separator
const returntext = levels.join(":");
console.log("Human Time:", returntext);
while (returntext.startsWith(":00")) {
returntext = returntext.substring(3);
} // Remove Prepending 0s (eg. 00:00:00:01:00)
if (returntext.startsWith(":0")) {
returntext = returntext.substring(2);
} else {
returntext = returntext.substring(1);
} // Prevent Time Starting With 0 (eg. 01:00)
if (!returntext.includes(":")) {
if (returntext.length == 1) {
returntext = "0" + returntext; // Make tens digit in seconds always visible (eg. 0:09)
}
returntext = "0:" + returntext; // Make minutes visible as 0 when sub 60 seconds (eg. 0:51)
}
let returntext = levels.join(":");
return returntext;
},
//--- End Convert Time To Human Readable String ---//

View file

@ -78,6 +78,9 @@ const searchModule = {
logger("codeRun", err, true);
callback(err);
});
},
showToast(text) {
Toast.show({ text: text });
}
};

View file

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<targetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="$USER_HOME$/.android/avd/Pixel_3a_API_31_arm64-v8a.avd" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2022-05-14T02:50:17.689302Z" />
</component>
</project>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -2,5 +2,9 @@
<widget version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<access origin="*" />
<feature name="CDVOrientation">
<param name="android-package" value="cordova.plugins.screenorientation.CDVOrientation"/>
</feature>
</widget>

View file

@ -0,0 +1,98 @@
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package cordova.plugins.screenorientation;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.json.JSONArray;
import org.json.JSONException;
import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.util.Log;
public class CDVOrientation extends CordovaPlugin {
private static final String TAG = "YoikScreenOrientation";
/**
* Screen Orientation Constants
*/
private static final String ANY = "any";
private static final String PORTRAIT_PRIMARY = "portrait-primary";
private static final String PORTRAIT_SECONDARY = "portrait-secondary";
private static final String LANDSCAPE_PRIMARY = "landscape-primary";
private static final String LANDSCAPE_SECONDARY = "landscape-secondary";
private static final String PORTRAIT = "portrait";
private static final String LANDSCAPE = "landscape";
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
Log.d(TAG, "execute action: " + action);
// Route the Action
if (action.equals("screenOrientation")) {
return routeScreenOrientation(args, callbackContext);
}
// Action not found
callbackContext.error("action not recognised");
return false;
}
private boolean routeScreenOrientation(JSONArray args, CallbackContext callbackContext) {
String action = args.optString(0);
String orientation = args.optString(1);
Log.d(TAG, "Requested ScreenOrientation: " + orientation);
Activity activity = cordova.getActivity();
if (orientation.equals(ANY)) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
} else if (orientation.equals(LANDSCAPE_PRIMARY)) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
} else if (orientation.equals(PORTRAIT_PRIMARY)) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
} else if (orientation.equals(LANDSCAPE)) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
} else if (orientation.equals(PORTRAIT)) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
} else if (orientation.equals(LANDSCAPE_SECONDARY)) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
} else if (orientation.equals(PORTRAIT_SECONDARY)) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
}
callbackContext.success();
return true;
}
}

View file

@ -352,15 +352,14 @@
DEVELOPMENT_TEAM = VRCJ7YWR89;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = com.Frontesque.vuetube;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MACCATALYST = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,6";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
@ -374,14 +373,13 @@
DEVELOPMENT_TEAM = VRCJ7YWR89;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.Frontesque.vuetube;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MACCATALYST = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,6";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};

View file

@ -2,5 +2,9 @@
<widget version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<access origin="*" />
<feature name="CDVOrientation">
<param name="ios-package" value="CDVOrientation"/>
</feature>
</widget>

View file

@ -9,17 +9,18 @@ install! 'cocoapods', :disable_input_output_paths => true
def capacitor_pods
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
pod 'CapacitorCommunityHttp', :path => '..\..\node_modules\@capacitor-community\http'
pod 'CapacitorApp', :path => '..\..\node_modules\@capacitor\app'
pod 'CapacitorBrowser', :path => '..\..\node_modules\@capacitor\browser'
pod 'CapacitorDevice', :path => '..\..\node_modules\@capacitor\device'
pod 'CapacitorFilesystem', :path => '..\..\node_modules\@capacitor\filesystem'
pod 'CapacitorHaptics', :path => '..\..\node_modules\@capacitor\haptics'
pod 'CapacitorShare', :path => '..\..\node_modules\@capacitor\share'
pod 'CapacitorSplashScreen', :path => '..\..\node_modules\@capacitor\splash-screen'
pod 'CapacitorStatusBar', :path => '..\..\node_modules\@capacitor\status-bar'
pod 'CapacitorToast', :path => '..\..\node_modules\@capacitor\toast'
pod 'HugotomaziCapacitorNavigationBar', :path => '..\..\node_modules\@hugotomazi\capacitor-navigation-bar'
pod 'CapacitorCommunityHttp', :path => '../../node_modules/@capacitor-community/http'
pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
pod 'CapacitorBrowser', :path => '../../node_modules/@capacitor/browser'
pod 'CapacitorDevice', :path => '../../node_modules/@capacitor/device'
pod 'CapacitorFilesystem', :path => '../../node_modules/@capacitor/filesystem'
pod 'CapacitorHaptics', :path => '../../node_modules/@capacitor/haptics'
pod 'CapacitorShare', :path => '../../node_modules/@capacitor/share'
pod 'CapacitorSplashScreen', :path => '../../node_modules/@capacitor/splash-screen'
pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar'
pod 'CapacitorToast', :path => '../../node_modules/@capacitor/toast'
pod 'HugotomaziCapacitorNavigationBar', :path => '../../node_modules/@hugotomazi/capacitor-navigation-bar'
pod 'CordovaPlugins', :path => '../capacitor-cordova-ios-plugins'
end
target 'App' do

View file

@ -28,22 +28,48 @@ Pronounced View Tube (<code>/ˈvjuːˌtjuːb/</code>)
<a href="https://twitter.com/VueTubeApp" alt="Twitter"><img src="https://img.shields.io/twitter/follow/VueTubeApp?label=Follow&style=flat&logo=twitter"></img></a>
</p>
Read this in other languages: [Español](readmeES.md)
## Features
<img src="/resources/vuetube.png" alt="VueTube icon" width="300"/>
- 🎨 Themes: Light, Dark, OLED, All the colors of the rainbow
- 🖌️ Customizable UI: You can fully customize the accent color, and other parts of the UI to remove features that you don't use!
- ⬆️ Auto Update: Be notified when an update is available & downgrade if you dislike it!
- 👁️ Tracking Protection: No telemetry is sent from your device by default
- 📺 Custom video player
- 👎 Return YouTube Dislike
## Install
<img src="/resources/install.png" alt="VueTube icon" width="300"/>
To install please visit www.vuetube.app/install
<details>
<summary>Or click here to display all versions avaiable</summary>
<br />
### Android
| <a href=https://nightly.link/VueTubeApp/VueTube/workflows/ci/main/android.zip><img id="im" width="200" src=/resources/getunstable.png></a> | <a href=https://cdn.discordapp.com/attachments/946910031562027029/972164599816273930/VueTube-Canary-May-6-2022.apk><img id="im" width="200" src=/resources/getcanary.png></a> | <a href=https://vuetube.app/install><img id="im" width="200" src=/resources/getstable.png></a> |
| ------------- | ------------- | ------------- |
| A lot of bugs, but early access to features | Less bugs than unstable, slightly more features than stable | Not available until the app becomes more developed |
### iOS
| <a href=https://nightly.link/VueTubeApp/VueTube/workflows/ci/main/iOS.zip><img id="im" width="200" src=/resources/getunstable.png></a> | <a href=https://cdn.discordapp.com/attachments/949908267855921163/972164558930198528/VueTube-Canary-May-6-2022.ipa><img id="im" width="200" src=/resources/getcanary.png></a> | <a href=https://vuetube.app/install><img id="im" width="200" src=/resources/getstable.png></a> |
| ------------- | ------------- | ------------- |
| A lot of bugs, but early access to features | Less bugs than unstable, slightly more features than stable | Not available until the app becomes more developed |
</details>
## Plans
<img src="/resources/plans.png" alt="VueTube icon" width="300"/>
- 🔍 Advanced Search
- 🗞️ Locally store watch history
- 📺 A custom video player
- ✂️ Shorts
- 🧑 Google account sign in
- 🖼️ Picture in picture mode
@ -53,6 +79,16 @@ To install please visit www.vuetube.app/install
View on our website: www.vuetube.app/info/screenshots
<details>
<summary> Or click here to display screenshots </summary>
<br />
<img src="https://vuetube.app/wtch.png" width="400">
<img src="https://vuetube.app/stng.png" width="400">
<img src="https://vuetube.app/srch.png" width="400">
</details>
### Technologies used
<a href="https://capacitorjs.com/solution/vue"><img src="https://cdn.discordapp.com/attachments/953538236716814356/955694368742834176/Capacitator-Dark.svg" height=40/></a> <a href="https://vuetifyjs.com/"><img src="https://cdn.discordapp.com/attachments/810799100940255260/973719873467342908/Vuetify-Dark.svg" height=40/></a> <a href="https://nuxtjs.org/"><img src="https://github.com/tandpfun/skill-icons/raw/main/icons/NuxtJS-Dark.svg" height=40/></a> <a href="https://vuejs.org/"><img src="https://github.com/tandpfun/skill-icons/raw/main/icons/VueJS-Dark.svg" height=40/></a> <a href="https://javascript.com/"><img src="https://github.com/tandpfun/skill-icons/raw/main/icons/JavaScript.svg" height=40/></a> <a href="https://java.com/"><img src="https://github.com/tandpfun/skill-icons/raw/main/icons/Java-Dark.svg" height=40/></a> <a href="https://gradle.com/"><img src="https://cdn.discordapp.com/attachments/810799100940255260/955691550560636958/Gradle.svg" height=40/></a> <a href="https://developer.apple.com/swift/"><img src="https://github.com/tandpfun/skill-icons/raw/main/icons/Swift.svg" height=40/></a>

116
readmeES.md Normal file
View file

@ -0,0 +1,116 @@
<p align="center">
<a href="https://vuetube.app/">
<img src="https://cdn.discordapp.com/attachments/751596360108605500/980418672331988992/VueTube_Dark.svg" alt="VueTube icon" width="500"/>
</a>
</br>
<sub>Logo por <a href="https://github.com/afnzmn">@afnzmn</a></sub>
</br>
</br>
<strong>Un cliente sencillo de streaming de vídeo FOSS diseñado para recrear TODAS las características de sus respectivas aplicaciones (y más) </strong>
</br>
Se pronuncia Viu Tuf (<code>/ˈvjuːˌtjuːb/</code>)
</p>
<p align="center">
<a href="https://github.com/VueTubeApp/VueTube/commits/main"><img src="https://img.shields.io/github/commit-activity/m/VueTubeApp/VueTube?label=Commits" alt="Commits"></img></a>
<a href="https://github.com/VueTubeApp/VueTube/issues" alt="Issues"><img src="https://img.shields.io/github/issues/VueTubeApp/VueTube"></img></a>
<a><img src="https://img.shields.io/github/languages/count/VueTubeApp/VueTube" alt="Languages"></img></a>
<a href="https://github.com/VueTubeApp/VueTube/blob/main/LICENSE" alt="License"><img src="https://img.shields.io/github/license/VueTubeApp/VueTube"></img></a>
<a><img src="https://img.shields.io/github/stars/VueTubeApp/VueTube" alt="Stars"></img></a>
<a><img src="https://img.shields.io/snyk/vulnerabilities/github/VueTubeApp/VueTube" alt="Vulnerabilities"></img></a>
<a><img src="https://img.shields.io/librariesio/github/VueTubeApp/VueTube" alt="Dependencies"></img></a>
<a><img src="https://img.shields.io/tokei/lines/github/VueTubeApp/VueTube" alt="Lines"></img></a>
<a href="https://github.com/VueTubeApp/VueTube/actions/workflows/ci.yml" alt="CI"><img src="https://github.com/VueTubeApp/VueTube/actions/workflows/ci.yml/badge.svg"></img></a>
<a href="https://vuetube.app" alt="Website"><img src="https://img.shields.io/website?down_message=offline&up_message=online&url=https%3A%2F%2Fvuetube.app"></img></a>
<a href="https://reddit.com/r/vuetube" alt="Reddit"><img src="https://img.shields.io/reddit/subreddit-subscribers/vuetube?label=r%2FVuetube&logo=reddit&logoColor=white"></img></a>
<a href="https://t.me/VueTube" alt="Telegram"><img src="https://img.shields.io/endpoint?color=neon&style=flat&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Fvuetube"></img></a>
<a href="https://discord.gg/7P8KJrdd5W" alt="Discord"><img src="https://img.shields.io/discord/946587366242533377?label=Discord&style=flat&logo=discord&logoColor=white"></img></a>
<a href="https://twitter.com/VueTubeApp" alt="Twitter"><img src="https://img.shields.io/twitter/follow/VueTubeApp?label=Follow&style=flat&logo=twitter"></img></a>
</p>
Leer en otros idiomas: [English](readme.md)
## Características
<img src="/resources/vuetubeES.PNG" alt="VueTube icon" width="300"/>
- 🎨 Temas: Claro, Oscuro, OLED, Todos los colores del arcoíris
- 🖌️ Interfaz personalizable: ¡Puedes personalizar completamente el color principal, y otras partes de la interfaz para eliminar características que no usas!
- ⬆️ Actualizaciones automáticas: ¡Recibe una notificación cuando haya una actualización disponible y baja de versión si no te gusta!
- 👁️ Protección contra el rastreo: No se envían datos desde tu dispositivo por defecto
- 📺 Reproductor de vídeo personalizado
- 👎 Return YouTube Dislike
## Instalar
<img src="/resources/installES.PNG" alt="VueTube icon" width="300"/>
Para instalar, por favor, visita www.vuetube.app/install
<details>
<summary>O haz clic aquí para mostrar todas las versiones disponibles</summary>
<br />
| <a href=https://nightly.link/VueTubeApp/VueTube/workflows/ci/main/android.zip><img id="im" width="200" src=/resources/getunstable.png></a> | <a href=https://cdn.discordapp.com/attachments/946910031562027029/972164599816273930/VueTube-Canary-May-6-2022.apk><img id="im" width="200" src=/resources/getcanary.png></a> | <a href=https://vuetube.app/install><img id="im" width="200" src=/resources/getstable.png></a> |
| ------------- | ------------- | ------------- |
| Un montón de bugs, pero acceso anticipado a funciones | Menos bugs que la inestable, aún así más funciones que la estable | No disponible hasta que la app este más desarrollada |
</details>
## Planes
<img src="/resources/plansES.PNG" alt="VueTube icon" width="300"/>
- 🔍 Búsqueda avanzada
- 🗞️ Historial de búsqueda local
- ✂️ Shorts (Cortos)
- 🧑 Inicio de sesión con tu cuenta de Google
- 🖼️ Modo Imagen en imagen
- ¡y más!
## Capturas de pantalla
Echalas un vistazo en nuestro sitio web: www.vuetube.app/info/screenshots
<details>
<summary> O haz clic aquí para mostrar las capturas </summary>
<br />
<img src="https://vuetube.app/wtch.png" width="400">
<img src="https://vuetube.app/stng.png" width="400">
<img src="https://vuetube.app/srch.png" width="400">
</details>
### Tecnologías usadas
<a href="https://capacitorjs.com/solution/vue"><img src="https://cdn.discordapp.com/attachments/953538236716814356/955694368742834176/Capacitator-Dark.svg" height=40/></a> <a href="https://vuetifyjs.com/"><img src="https://cdn.discordapp.com/attachments/810799100940255260/973719873467342908/Vuetify-Dark.svg" height=40/></a> <a href="https://nuxtjs.org/"><img src="https://github.com/tandpfun/skill-icons/raw/main/icons/NuxtJS-Dark.svg" height=40/></a> <a href="https://vuejs.org/"><img src="https://github.com/tandpfun/skill-icons/raw/main/icons/VueJS-Dark.svg" height=40/></a> <a href="https://javascript.com/"><img src="https://github.com/tandpfun/skill-icons/raw/main/icons/JavaScript.svg" height=40/></a> <a href="https://java.com/"><img src="https://github.com/tandpfun/skill-icons/raw/main/icons/Java-Dark.svg" height=40/></a> <a href="https://gradle.com/"><img src="https://cdn.discordapp.com/attachments/810799100940255260/955691550560636958/Gradle.svg" height=40/></a> <a href="https://developer.apple.com/swift/"><img src="https://github.com/tandpfun/skill-icons/raw/main/icons/Swift.svg" height=40/></a>
### ¿Porque estoy haciendo esto?
Bueno, esto ha estado en el servidor de Discord de Return YouTube Dislike durante bastante tiempo, ¡así que pensé que probablemente debería lanzarlo!
### ¿Quieres contribuir?
Por favor, lee en nuestro sitio web cómo hacerlo: www.vuetube.app/contributing
## Colaboradores
<a href="https://github.com/VueTubeApp/VueTube/graphs/contributors">
<img src="https://contrib.rocks/image?repo=VueTubeApp/VueTube" />
</a>
<sub>Hecho con [contrib.rocks](https://contrib.rocks). </sub>
## Agradecimientos
- Emojis por el [equipo de Twemoji](https://twemoji.twitter.com/), Con licencia [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/)
- Logo de VueTube por [@afnzmn](https://github.com/afnzmn)
## Aviso legal
El proyecto VueTube y sus contenidos no están afiliados, financiados, autorizados, respaldados o asociados de ninguna manera con YouTube, Google LLC o cualquiera de sus filiales y subsidiarias. El sitio web oficial de YouTube se encuentra en [www.youtube.com](https://www.youtube.com).
Cualquier marca comercial, de servicio, nombre comercial u otros derechos de propiedad intelectual utilizados en el proyecto VueTube son propiedad de sus respectivos dueños.
En caso de conflicto entre las traducciones del aviso legal, tiene preferencia la versión en inglés.

View file

Before

Width:  |  Height:  |  Size: 949 B

After

Width:  |  Height:  |  Size: 949 B

1
resources/Stable.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" fill="none" viewBox="0 0 512 512"><rect width="512" height="512" fill="url(#paint0_linear_6_11)"/><path fill="url(#paint1_linear_6_11)" fill-rule="evenodd" d="M327.768 269.391C338.454 263.236 338.454 247.814 327.766 241.66L327.761 241.658L291.527 220.81L331.399 202.218L345.714 210.453L345.736 210.466L386.392 233.857C403.102 243.471 403.102 267.582 386.392 277.196L345.743 300.582L345.714 300.599L328.778 310.343L288.905 291.75L327.761 269.395L327.768 269.391ZM206 290.605V311.783V311.79C206.005 324.092 219.314 331.788 229.979 325.652L252.89 312.471L292.763 331.063L247.932 356.856L247.903 356.873L207.467 380.137C190.801 389.725 170 377.695 170 358.467L170 311.783L170 273.818L206 290.605ZM295.384 181.497L255.512 200.09L229.979 185.4L229.964 185.392C219.3 179.27 206 186.968 206 199.269V223.177L170 239.965L170 199.269L170 152.585C170 133.357 190.801 121.327 207.467 130.915L247.917 154.187L247.932 154.196L295.384 181.497Z" clip-rule="evenodd"/><defs><linearGradient id="paint0_linear_6_11" x1="0" x2="512" y1="0" y2="512" gradientUnits="userSpaceOnUse"><stop stop-color="#1B3245"/><stop offset="1" stop-color="#0C2028"/></linearGradient><linearGradient id="paint1_linear_6_11" x1="170" x2="431" y1="128" y2="389" gradientUnits="userSpaceOnUse"><stop stop-color="#00E1C3"/><stop offset="1" stop-color="#00D1D5"/></linearGradient></defs></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
resources/getcanary.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
resources/getstable.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
resources/getunstable.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
resources/install.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
resources/installES.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
resources/plans.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
resources/plansES.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

BIN
resources/vuetube.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
resources/vuetubeES.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB