This commit is contained in:
Jason 2024-11-20 16:01:01 +00:00 committed by GitHub
commit 7e6ee88aac
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',
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
@ -1648,7 +1674,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)
}
}
@ -1733,7 +1759,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)
@ -2517,6 +2543,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(
@ -2703,6 +2735,12 @@ export default defineComponent({
*/
async function destroyPlayer() {
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
@ -2755,6 +2793,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

@ -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