Compare commits

...

18 Commits

Author SHA1 Message Date
Jason
d66e54f3f8
Merge 997fbdbbba into a70a5c6ab2 2024-11-21 09:00:40 +00:00
Gideon Wentink
a70a5c6ab2
Translated using Weblate (Afrikaans)
Currently translated at 31.9% (272 of 852 strings)

Translation: FreeTube/Translations
Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/af/
2024-11-21 10:00:20 +01:00
Jason Henriquez
997fbdbbba Update values to check IS_ELECTRON 2024-10-28 09:21:58 -05:00
Jason Henriquez
fa35784522 Fix linting 2024-10-28 09:09:39 -05:00
Jason Henriquez
687ef1e889 Merge branch 'development' of github.com:FreeTubeApp/FreeTube into feat/preferred-viewing-mode 2024-10-28 09:07:21 -05:00
Jason Henriquez
b548532ea7 Improve stay-in-mode handling to save state values on player destroy
This allows staying in PiP when clicking on other videos, staying in fullscreen/fullwindow when using Ctrl+Left Arrow / Ctrl+Right Arrow, and staying in PiP when using the watch-video-playlist Play Prev / Play Next buttons.
2024-10-28 09:04:09 -05:00
Jason Henriquez
f71e9cefd6 Remove PiP and fullscreen default viewing mode options in settings when Electron is not available 2024-10-28 08:15:21 -05:00
Jason Henriquez
bb3be09122 Clean up External Player Default Viewing Mode link template logic 2024-10-23 21:10:49 -05:00
Jason Henriquez
5a54142c70 Update to use expand icon instead 2024-10-23 20:36:14 -05:00
Jason Henriquez
d57111aea6 Update to trigger setFullWindow event when starting in fullwindow 2024-10-23 08:02:49 -05:00
Jason
5ca47a6349
Update src/renderer/components/player-settings/player-settings.js
Co-authored-by: absidue <48293849+absidue@users.noreply.github.com>
2024-10-23 07:30:46 -05:00
Jason Henriquez
937935f06a Revert Enable Theater Mode by Default removal
Theatre mode is not mutually exclusive with the viewing mode and thus should not be included here. This also saves us the work of having to update the default viewing mode to theatre mode on first load for 1-2 releases that we would have otherwise needed.
2024-10-20 20:03:05 -05:00
Jason Henriquez
0347336647 Fix fullscreen issue with icons by calling requestFullscreen on videoContainer element 2024-10-20 17:52:01 -05:00
Jason Henriquez
db27c7b923 Disable & hide 'External Player' default viewing mode when no external player is set
This will prevent issues with users who accidentally change this setting and report that clicking on videos results in errors.
2024-10-20 13:11:39 -05:00
Jason Henriquez
2f1986ca53 Implement external player default viewing mode
Current limitations: does not work for the search bar, randomly encountered YT video links (e.g., in descriptions), or the video thumbnail link in the playlist list view.
2024-10-20 13:11:39 -05:00
Jason Henriquez
b64d9896d4 Implement Default Viewing Mode setting 2024-10-20 13:11:39 -05:00
Jason Henriquez
e95426d193 Have fullscreen and PiP be re-requested on autoplay when they are open 2024-10-20 13:11:30 -05:00
Jason Henriquez
b8c6a40a36 MHave fullscreen persist when videos autoplay 2024-10-19 21:20:40 -05:00
16 changed files with 218 additions and 51 deletions

View File

@ -15,6 +15,9 @@ const IpcChannels = {
APP_READY: 'app-ready',
RELAUNCH_REQUEST: 'relaunch-request',
REQUEST_FULLSCREEN: 'request-fullscreen',
REQUEST_PIP: 'request-pip',
SEARCH_INPUT_HANDLING_READY: 'search-input-handling-ready',
UPDATE_SEARCH_INPUT_TEXT: 'update-search-input-text',

View File

@ -928,6 +928,14 @@ function runApp() {
return app.getPath('pictures')
})
ipcMain.on(IpcChannels.REQUEST_FULLSCREEN, ({ sender }) => {
sender.executeJavaScript('document.getElementById("videoContainer").requestFullscreen({navigationUI: "hide"})', true)
})
ipcMain.on(IpcChannels.REQUEST_PIP, ({ sender }) => {
sender.executeJavaScript('document.getElementById("video").requestPictureInPicture()', true)
})
ipcMain.handle(IpcChannels.SHOW_OPEN_DIALOG, async ({ sender }, options) => {
const senderWindow = findSenderWindow(sender)
if (senderWindow) {

View File

@ -203,7 +203,6 @@ export default defineComponent({
'updateHideLiveChat',
'updateHideActiveSubscriptions',
'updatePlayNextVideo',
'updateDefaultTheatreMode',
'updateHideVideoDescription',
'updateHideComments',
'updateHideCommentPhotos',

View File

@ -377,6 +377,10 @@ export default defineComponent({
return this.$store.getters.getExternalPlayer
},
externalPlayerIsDefaultViewingMode: function () {
return this.externalPlayer !== '' && this.$store.getters.getDefaultViewingMode === 'external_player'
},
defaultPlayback: function () {
return this.$store.getters.getDefaultPlayback
},
@ -479,13 +483,18 @@ export default defineComponent({
return this.isInQuickBookmarkPlaylist ? 'base favorite' : 'base'
},
watchPageLinkTo() {
// For `router-link` attribute `to`
watchVideoRoute() {
return {
path: `/watch/${this.id}`,
query: this.watchPageLinkQuery,
}
},
// For `router-link` attribute `to`
watchVideoRouterLink() {
return !this.externalPlayerIsDefaultViewingMode ? this.watchVideoRoute : {}
},
watchPageLinkQuery() {
const query = {}
if (this.playlistIdFinal) { query.playlistId = this.playlistIdFinal }
@ -531,6 +540,11 @@ export default defineComponent({
}
},
methods: {
handleWatchPageLinkClick: function() {
if (this.externalPlayerIsDefaultViewingMode) {
this.handleExternalPlayer()
}
},
fetchDeArrowThumbnail: async function() {
if (this.thumbnailPreference === 'hidden') { return }
const videoId = this.id

View File

@ -14,7 +14,8 @@
<router-link
class="thumbnailLink"
tabindex="-1"
:to="watchPageLinkTo"
:to="watchVideoRouterLink"
@click.native="handleWatchPageLinkClick"
>
<img
:src="thumbnail"
@ -34,7 +35,7 @@
{{ isLive ? $t("Video.Live") : (isUpcoming ? $t("Video.Upcoming") : displayDuration) }}
</div>
<ft-icon-button
v-if="externalPlayer !== ''"
v-if="externalPlayer !== '' && !externalPlayerIsDefaultViewingMode"
:title="$t('Video.External Player.OpenInTemplate', { externalPlayer })"
:icon="['fas', 'external-link-alt']"
class="externalPlayerIcon"
@ -112,7 +113,8 @@
<div class="info">
<router-link
class="title"
:to="watchPageLinkTo"
:to="watchVideoRouterLink"
@click.native="handleWatchPageLinkClick"
>
<h3 class="h3Title">
{{ displayTitle }}

View File

@ -104,12 +104,25 @@ export default defineComponent({
vrProjection: {
type: String,
default: null
},
startInFullscreen: {
type: Boolean,
default: false
},
startInFullwindow: {
type: Boolean,
default: false
},
startInPip: {
type: Boolean,
default: false
}
},
emits: [
'error',
'loaded',
'ended',
'player-destroyed',
'timeupdate',
'toggle-theatre-mode'
],
@ -139,11 +152,15 @@ export default defineComponent({
const isLive = ref(false)
const useOverFlowMenu = ref(false)
const fullWindowEnabled = ref(false)
const forceAspectRatio = ref(false)
const activeLegacyFormat = shallowRef(null)
const fullWindowEnabled = ref(false)
const startInFullwindow = props.startInFullwindow
let startInFullscreen = props.startInFullscreen
let startInPip = props.startInPip
/**
* @type {{
* url: string,
@ -1046,6 +1063,15 @@ export default defineComponent({
emit('ended')
}
function handleCanPlay() {
// PiP can only be activated once the video's readState and video track are populated
if (startInPip && ui.getControls().isPiPAllowed() && process.env.IS_ELECTRON) {
startInPip = false
const { ipcRenderer } = require('electron')
ipcRenderer.send(IpcChannels.REQUEST_PIP)
}
}
function updateVolume() {
const video_ = video.value
// https://docs.videojs.com/html5#volume
@ -1645,7 +1671,7 @@ export default defineComponent({
*/
class FullWindowButtonFactory {
create(rootElement, controls) {
return new FullWindowButton(fullWindowEnabled.value, events, rootElement, controls)
return new FullWindowButton(fullWindowEnabled.value, startInFullwindow, events, rootElement, controls)
}
}
@ -1730,7 +1756,7 @@ export default defineComponent({
/**
* As shaka-player doesn't let you unregister custom control factories,
* overwrite them with `null` instead so the referenced objects
* (e.g. {@linkcode events}, {@linkcode fullWindowEnabled}) can get gargabe collected
* (e.g. {@linkcode events}, {@linkcode fullWindowEnabled}) can get garbage collected
*/
function cleanUpCustomPlayerControls() {
shakaControls.registerElement('ft_audio_tracks', null)
@ -2525,6 +2551,12 @@ export default defineComponent({
if (props.chapters.length > 0) {
createChapterMarkers()
}
if (startInFullscreen && process.env.IS_ELECTRON) {
startInFullscreen = false
const { ipcRenderer } = require('electron')
ipcRenderer.send(IpcChannels.REQUEST_FULLSCREEN)
}
}
watch(
@ -2741,6 +2773,12 @@ export default defineComponent({
ignoreErrors = true
if (ui) {
if (ui.getControls()) {
// save the state of player settings to reinitialize them upon next creation
const controls = ui.getControls()
emit('player-destroyed', controls.isFullScreenEnabled(), fullWindowEnabled.value, controls.isPiPEnabled())
}
// destroying the ui also destroys the player
await ui.destroy()
ui = null
@ -2793,6 +2831,7 @@ export default defineComponent({
handlePlay,
handlePause,
handleCanPlay,
handleEnded,
updateVolume,
handleTimeupdate,

View File

@ -1,5 +1,6 @@
<template>
<div
id="videoContainer"
ref="container"
class="ftVideoPlayer shaka-video-container"
:class="{
@ -9,6 +10,7 @@
>
<!-- eslint-disable-next-line vuejs-accessibility/media-has-caption -->
<video
id="video"
ref="video"
class="player"
preload="auto"
@ -19,6 +21,7 @@
@play="handlePlay"
@pause="handlePause"
@ended="handleEnded"
@canplay="handleCanPlay"
@volumechange="updateVolume"
@timeupdate="handleTimeupdate"
/>

View File

@ -5,11 +5,12 @@ import i18n from '../../../i18n/index'
export class FullWindowButton extends shaka.ui.Element {
/**
* @param {boolean} fullWindowEnabled
* @param {boolean} startInFullWindow
* @param {EventTarget} events
* @param {HTMLElement} parent
* @param {shaka.ui.Controls} controls
*/
constructor(fullWindowEnabled, events, parent, controls) {
constructor(fullWindowEnabled, startInFullWindow, events, parent, controls) {
super(parent, controls)
/** @private */
@ -62,6 +63,12 @@ export class FullWindowButton extends shaka.ui.Element {
this.updateLocalisedStrings_()
})
if (startInFullWindow) {
events.dispatchEvent(new CustomEvent('setFullWindow', {
detail: true
}))
}
this.updateLocalisedStrings_()
}

View File

@ -60,6 +60,13 @@ export default defineComponent({
screenshotFolderPlaceholder: '',
screenshotFilenameExample: '',
screenshotDefaultPattern: '%Y%M%D-%H%N%S',
viewingModeValues: [
'default',
...(process.env.IS_ELECTRON ? ['fullscreen'] : []),
'fullwindow',
...(process.env.IS_ELECTRON ? ['pip'] : []),
'external_player'
]
}
},
computed: {
@ -119,10 +126,24 @@ export default defineComponent({
return this.$store.getters.getDefaultQuality
},
defaultViewingMode: function () {
const defaultViewingMode = this.$store.getters.getDefaultViewingMode
if ((defaultViewingMode === 'external_player' && this.externalPlayer === '') ||
(!process.env.IS_ELECTRON && (defaultViewingMode === 'fullscreen' || defaultViewingMode === 'pip'))) {
return 'default'
}
return defaultViewingMode
},
defaultTheatreMode: function () {
return this.$store.getters.getDefaultTheatreMode
},
externalPlayer: function () {
return this.$store.getters.getExternalPlayer
},
hideRecommendedVideos: function () {
return this.$store.getters.getHideRecommendedVideos
},
@ -177,6 +198,23 @@ export default defineComponent({
]
},
viewingModeNames: function () {
const viewingModeNames = [
this.$t('Settings.General Settings.Thumbnail Preference.Default'),
...(process.env.IS_ELECTRON ? [this.$t('Settings.Player Settings.Default Viewing Mode.Full Screen')] : []),
this.$t('Video.Player.Full Window'),
...(process.env.IS_ELECTRON ? [this.$t('Settings.Player Settings.Default Viewing Mode.Picture in Picture')] : [])
]
if (this.externalPlayer !== '') {
viewingModeNames.push(
this.$t('Settings.Player Settings.Default Viewing Mode.External Player', { externalPlayerName: this.externalPlayer })
)
}
return viewingModeNames
},
enableScreenshot: function() {
return this.$store.getters.getEnableScreenshot
},
@ -289,11 +327,12 @@ export default defineComponent({
'updatePlayNextVideo',
'updateEnableSubtitlesByDefault',
'updateProxyVideos',
'updateDefaultTheatreMode',
'updateDefaultViewingMode',
'updateDefaultSkipInterval',
'updateDefaultInterval',
'updateDefaultVolume',
'updateDefaultPlayback',
'updateDefaultTheatreMode',
'updateDefaultVideoFormat',
'updateDefaultQuality',
'updateVideoVolumeMouseScroll',

View File

@ -81,6 +81,41 @@
/>
</div>
</div>
<ft-flex-box>
<ft-select
:placeholder="$t('Settings.Player Settings.Default Viewing Mode.Default Viewing Mode')"
:value="defaultViewingMode"
:select-names="viewingModeNames"
:select-values="viewingModeValues"
:icon="['fas', 'expand']"
@change="updateDefaultViewingMode"
/>
<ft-select
:placeholder="$t('Settings.Player Settings.Default Video Format.Default Video Format')"
:value="defaultVideoFormat"
:select-names="formatNames"
:select-values="formatValues"
:tooltip="$t('Tooltips.Player Settings.Default Video Format')"
:icon="['fas', 'file-video']"
@change="updateDefaultVideoFormat"
/>
<ft-select
:placeholder="$t('Settings.Player Settings.Default Quality.Default Quality')"
:value="defaultQuality"
:select-names="qualityNames"
:select-values="qualityValues"
:icon="['fas', 'photo-film']"
@change="updateDefaultQuality"
/>
<ft-select
:placeholder="$t('Settings.Player Settings.Video Playback Rate Interval')"
:value="videoPlaybackRateInterval"
:select-names="playbackRateIntervalValues"
:select-values="playbackRateIntervalValues"
:icon="['fas', 'gauge']"
@change="updateVideoPlaybackRateInterval"
/>
</ft-flex-box>
<ft-flex-box>
<ft-slider
:label="$t('Settings.Player Settings.Fast-Forward / Rewind Interval')"
@ -127,33 +162,6 @@
value-extension="x"
@change="updateMaxVideoPlaybackRate"
/>
<ft-select
:placeholder="$t('Settings.Player Settings.Video Playback Rate Interval')"
:value="videoPlaybackRateInterval"
:select-names="playbackRateIntervalValues"
:select-values="playbackRateIntervalValues"
:icon="['fas', 'gauge']"
@change="updateVideoPlaybackRateInterval"
/>
</ft-flex-box>
<ft-flex-box>
<ft-select
:placeholder="$t('Settings.Player Settings.Default Video Format.Default Video Format')"
:value="defaultVideoFormat"
:select-names="formatNames"
:select-values="formatValues"
:tooltip="$t('Tooltips.Player Settings.Default Video Format')"
:icon="['fas', 'file-video']"
@change="updateDefaultVideoFormat"
/>
<ft-select
:placeholder="$t('Settings.Player Settings.Default Quality.Default Quality')"
:value="defaultQuality"
:select-names="qualityNames"
:select-values="qualityValues"
:icon="['fas', 'photo-film']"
@change="updateDefaultQuality"
/>
</ft-flex-box>
<br>
<ft-flex-box

View File

@ -45,6 +45,7 @@ import {
faEnvelope,
faExchangeAlt,
faExclamationCircle,
faExpand,
faExternalLinkAlt,
faEye,
faEyeSlash,
@ -158,6 +159,7 @@ library.add(
faEnvelope,
faExchangeAlt,
faExclamationCircle,
faExpand,
faExternalLinkAlt,
faEye,
faEyeSlash,

View File

@ -179,6 +179,7 @@ const state = {
defaultQuality: '720',
defaultSkipInterval: 5,
defaultTheatreMode: false,
defaultViewingMode: 'default',
defaultVideoFormat: 'dash',
disableSmoothScrolling: false,
displayVideoPlayButton: false,

View File

@ -66,6 +66,9 @@ export default defineComponent({
},
data: function () {
return {
startNextVideoInFullscreen: false,
startNextVideoInFullwindow: false,
startNextVideoInPip: false,
isLoading: true,
firstLoad: true,
useTheatreMode: false,
@ -166,6 +169,12 @@ export default defineComponent({
defaultTheatreMode: function () {
return this.$store.getters.getDefaultTheatreMode
},
defaultViewingMode: function () {
return this.$store.getters.getDefaultViewingMode
},
externalPlayer: function () {
return this.$store.getters.getExternalPlayer
},
defaultVideoFormat: function () {
return this.$store.getters.getDefaultVideoFormat
},
@ -313,7 +322,7 @@ export default defineComponent({
this.checkIfPlaylist()
// this has to be below checkIfPlaylist() as theatrePossible needs to know if there is a playlist or not
this.useTheatreMode = this.defaultTheatreMode && this.theatrePossible
this.setViewingModeOnFirstLoad()
if (!process.env.SUPPORTS_LOCAL_API || this.backendPreference === 'invidious') {
this.getVideoInformationInvidious()
@ -324,6 +333,21 @@ export default defineComponent({
window.addEventListener('beforeunload', this.handleWatchProgress)
},
setViewingModeOnFirstLoad: function () {
this.useTheatreMode = this.defaultTheatreMode && this.theatrePossible
switch (this.defaultViewingMode) {
case 'fullscreen':
this.startNextVideoInFullscreen = true
return
case 'fullwindow':
this.startNextVideoInFullwindow = true
return
case 'pip':
this.startNextVideoInPip = true
}
},
changeTimestamp: function (timestamp) {
const player = this.$refs.player
@ -1228,6 +1252,12 @@ export default defineComponent({
this.activeFormat = 'audio'
},
handlePlayerDestroyed: function(startNextVideoInFullscreen = false, startNextVideoInFullwindow = false, startNextVideoInPip = false) {
this.startNextVideoInFullscreen = startNextVideoInFullscreen
this.startNextVideoInFullwindow = startNextVideoInFullwindow
this.startNextVideoInPip = startNextVideoInPip
},
handleVideoEnded: function () {
if ((!this.watchingPlaylist || !this.autoplayPlaylists) && !this.playNextVideo) {
return

View File

@ -34,10 +34,14 @@
:theatre-possible="theatrePossible"
:use-theatre-mode="useTheatreMode"
:vr-projection="vrProjection"
:start-in-fullscreen="startNextVideoInFullscreen"
:start-in-fullwindow="startNextVideoInFullwindow"
:start-in-pip="startNextVideoInPip"
class="videoPlayer"
@error="handlePlayerError"
@loaded="handleVideoLoaded"
@timeupdate="updateCurrentChapter"
@player-destroyed="handlePlayerDestroyed"
@ended="handleVideoEnded"
@toggle-theatre-mode="useTheatreMode = !useTheatreMode"
/>

View File

@ -686,20 +686,23 @@ Channel:
Shorts:
This channel does not currently have any shorts: ''
Live:
Live: ''
This channel does not currently have any live streams: ''
Live: 'Regstreeks'
This channel does not currently have any live streams: 'Hierdie kanaal het tans
geen rekstreekse stromings nie'
Playlists:
Playlists: ''
This channel does not currently have any playlists: ''
Playlists: 'Afspeellyste'
This channel does not currently have any playlists: 'Hierdie kanaal het tans geen
afspeellyste nie'
Sort Types:
Last Video Added: ''
Newest: ''
Oldest: ''
Last Video Added: 'Laaste toegevoegde video'
Newest: 'Nuutste'
Oldest: 'Oudste'
Podcasts:
Podcasts: ''
This channel does not currently have any podcasts: ''
Podcasts: 'Podsendings'
This channel does not currently have any podcasts: 'Hierdie kanaal het tans geen
podsendings nie'
Releases:
Releases: ''
Releases: 'Vrystellings'
This channel does not currently have any releases: 'Hierdie kanaal het nie tans
enige vrystellings nie'
About:

View File

@ -392,11 +392,16 @@ Settings:
Player Settings:
Player Settings: Player
Play Next Video: Play Next Video
Turn on Subtitles by Default: Turn on Subtitles by Default
Turn on Subtitles by Default: Enable Subtitles by Default
Autoplay Videos: Autoplay Videos
Proxy Videos Through Invidious: Proxy Videos Through Invidious
Autoplay Playlists: Autoplay Playlists
Enable Theatre Mode by Default: Enable Theatre Mode by Default
Enable Theatre Mode by Default: Enable Theater Mode by Default
Default Viewing Mode:
Default Viewing Mode: Default Viewing Mode
Full Screen: Full Screen
Picture in Picture: Picture in Picture
External Player: External Player ({externalPlayerName})
Scroll Volume Over Video Player: Scroll Volume Over Video Player
Scroll Playback Rate Over Video Player: Scroll Playback Rate Over Video Player
Skip by Scrolling Over Video Player: Skip by Scrolling Over Video Player