This commit is contained in:
Jason 2024-11-21 09:00:40 +00:00 committed by GitHub
commit d66e54f3f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 205 additions and 41 deletions

View File

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

View File

@ -928,6 +928,14 @@ function runApp() {
return app.getPath('pictures') 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) => { ipcMain.handle(IpcChannels.SHOW_OPEN_DIALOG, async ({ sender }, options) => {
const senderWindow = findSenderWindow(sender) const senderWindow = findSenderWindow(sender)
if (senderWindow) { if (senderWindow) {

View File

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

View File

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

View File

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

View File

@ -104,12 +104,25 @@ export default defineComponent({
vrProjection: { vrProjection: {
type: String, type: String,
default: null default: null
},
startInFullscreen: {
type: Boolean,
default: false
},
startInFullwindow: {
type: Boolean,
default: false
},
startInPip: {
type: Boolean,
default: false
} }
}, },
emits: [ emits: [
'error', 'error',
'loaded', 'loaded',
'ended', 'ended',
'player-destroyed',
'timeupdate', 'timeupdate',
'toggle-theatre-mode' 'toggle-theatre-mode'
], ],
@ -139,11 +152,15 @@ export default defineComponent({
const isLive = ref(false) const isLive = ref(false)
const useOverFlowMenu = ref(false) const useOverFlowMenu = ref(false)
const fullWindowEnabled = ref(false)
const forceAspectRatio = ref(false) const forceAspectRatio = ref(false)
const activeLegacyFormat = shallowRef(null) const activeLegacyFormat = shallowRef(null)
const fullWindowEnabled = ref(false)
const startInFullwindow = props.startInFullwindow
let startInFullscreen = props.startInFullscreen
let startInPip = props.startInPip
/** /**
* @type {{ * @type {{
* url: string, * url: string,
@ -1046,6 +1063,15 @@ export default defineComponent({
emit('ended') 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() { function updateVolume() {
const video_ = video.value const video_ = video.value
// https://docs.videojs.com/html5#volume // https://docs.videojs.com/html5#volume
@ -1645,7 +1671,7 @@ export default defineComponent({
*/ */
class FullWindowButtonFactory { class FullWindowButtonFactory {
create(rootElement, controls) { 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, * As shaka-player doesn't let you unregister custom control factories,
* overwrite them with `null` instead so the referenced objects * 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() { function cleanUpCustomPlayerControls() {
shakaControls.registerElement('ft_audio_tracks', null) shakaControls.registerElement('ft_audio_tracks', null)
@ -2525,6 +2551,12 @@ export default defineComponent({
if (props.chapters.length > 0) { if (props.chapters.length > 0) {
createChapterMarkers() createChapterMarkers()
} }
if (startInFullscreen && process.env.IS_ELECTRON) {
startInFullscreen = false
const { ipcRenderer } = require('electron')
ipcRenderer.send(IpcChannels.REQUEST_FULLSCREEN)
}
} }
watch( watch(
@ -2741,6 +2773,12 @@ export default defineComponent({
ignoreErrors = true ignoreErrors = true
if (ui) { 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 // destroying the ui also destroys the player
await ui.destroy() await ui.destroy()
ui = null ui = null
@ -2793,6 +2831,7 @@ export default defineComponent({
handlePlay, handlePlay,
handlePause, handlePause,
handleCanPlay,
handleEnded, handleEnded,
updateVolume, updateVolume,
handleTimeupdate, handleTimeupdate,

View File

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

View File

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

View File

@ -60,6 +60,13 @@ export default defineComponent({
screenshotFolderPlaceholder: '', screenshotFolderPlaceholder: '',
screenshotFilenameExample: '', screenshotFilenameExample: '',
screenshotDefaultPattern: '%Y%M%D-%H%N%S', screenshotDefaultPattern: '%Y%M%D-%H%N%S',
viewingModeValues: [
'default',
...(process.env.IS_ELECTRON ? ['fullscreen'] : []),
'fullwindow',
...(process.env.IS_ELECTRON ? ['pip'] : []),
'external_player'
]
} }
}, },
computed: { computed: {
@ -119,10 +126,24 @@ export default defineComponent({
return this.$store.getters.getDefaultQuality 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 () { defaultTheatreMode: function () {
return this.$store.getters.getDefaultTheatreMode return this.$store.getters.getDefaultTheatreMode
}, },
externalPlayer: function () {
return this.$store.getters.getExternalPlayer
},
hideRecommendedVideos: function () { hideRecommendedVideos: function () {
return this.$store.getters.getHideRecommendedVideos 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() { enableScreenshot: function() {
return this.$store.getters.getEnableScreenshot return this.$store.getters.getEnableScreenshot
}, },
@ -289,11 +327,12 @@ export default defineComponent({
'updatePlayNextVideo', 'updatePlayNextVideo',
'updateEnableSubtitlesByDefault', 'updateEnableSubtitlesByDefault',
'updateProxyVideos', 'updateProxyVideos',
'updateDefaultTheatreMode', 'updateDefaultViewingMode',
'updateDefaultSkipInterval', 'updateDefaultSkipInterval',
'updateDefaultInterval', 'updateDefaultInterval',
'updateDefaultVolume', 'updateDefaultVolume',
'updateDefaultPlayback', 'updateDefaultPlayback',
'updateDefaultTheatreMode',
'updateDefaultVideoFormat', 'updateDefaultVideoFormat',
'updateDefaultQuality', 'updateDefaultQuality',
'updateVideoVolumeMouseScroll', 'updateVideoVolumeMouseScroll',

View File

@ -81,6 +81,41 @@
/> />
</div> </div>
</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-flex-box>
<ft-slider <ft-slider
:label="$t('Settings.Player Settings.Fast-Forward / Rewind Interval')" :label="$t('Settings.Player Settings.Fast-Forward / Rewind Interval')"
@ -127,33 +162,6 @@
value-extension="x" value-extension="x"
@change="updateMaxVideoPlaybackRate" @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> </ft-flex-box>
<br> <br>
<ft-flex-box <ft-flex-box

View File

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

View File

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

View File

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

View File

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

View File

@ -392,11 +392,16 @@ Settings:
Player Settings: Player Settings:
Player Settings: Player Player Settings: Player
Play Next Video: Play Next Video 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 Autoplay Videos: Autoplay Videos
Proxy Videos Through Invidious: Proxy Videos Through Invidious Proxy Videos Through Invidious: Proxy Videos Through Invidious
Autoplay Playlists: Autoplay Playlists 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 Volume Over Video Player: Scroll Volume Over Video Player
Scroll Playback Rate Over Video Player: Scroll Playback Rate 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 Skip by Scrolling Over Video Player: Skip by Scrolling Over Video Player