2020-02-16 18:30:00 +00:00
|
|
|
import Vue from 'vue'
|
2021-01-13 03:56:31 +00:00
|
|
|
import { mapActions } from 'vuex'
|
2020-02-16 18:30:00 +00:00
|
|
|
import FtCard from '../ft-card/ft-card.vue'
|
|
|
|
|
2020-02-20 20:58:21 +00:00
|
|
|
import $ from 'jquery'
|
2020-02-16 18:30:00 +00:00
|
|
|
import videojs from 'video.js'
|
2020-02-18 20:59:01 +00:00
|
|
|
import qualitySelector from '@silvermine/videojs-quality-selector'
|
2020-11-13 22:29:41 +00:00
|
|
|
import fs from 'fs'
|
2020-11-15 19:13:18 +00:00
|
|
|
import 'videojs-overlay/dist/videojs-overlay'
|
|
|
|
import 'videojs-overlay/dist/videojs-overlay.css'
|
2020-09-01 10:04:54 +00:00
|
|
|
import 'videojs-vtt-thumbnails-freetube'
|
2020-02-18 20:59:01 +00:00
|
|
|
import 'videojs-contrib-quality-levels'
|
|
|
|
import 'videojs-http-source-selector'
|
2020-02-16 18:30:00 +00:00
|
|
|
|
Store Revamp / Full database synchronization across windows (#1833)
* History: Refactor history module
* Profiles: Refactor profiles module
* IPC: Move channel ids to their own file and make them constants
* IPC: Replace single sync channel for one channel per sync type
* Everywhere: Replace default profile id magic strings with constant ref
* Profiles: Refactor `activeProfile` property from store
This commit makes it so that `activeProfile`'s getter returns
the entire profile, while the related update function only needs
the profile id (instead of the previously used array index)
to change the currently active profile.
This change was made due to inconsistency regarding the active profile
when creating new profiles.
If a new profile coincidentally landed in the current active profile's
array index after sorting, the app would mistakenly change to it
without any action from the user apart from the profile's creation.
Turning the profile id into the selector instead solves this issue.
* Revert "Store: Implement history synchronization between windows"
This reverts commit 99b61e617873412eb393d8f4dfccd8f8c172021f.
This is necessary for an upcoming improved implementation of the
history synchronization.
* History: Remove unused mutation
* Everywhere: Create abstract database handlers
The project now utilizes abstract handlers to fetch, modify
or otherwise manipulate data from the database.
This facilitates 3 aspects of the app, in addition of
making them future proof:
- Switching database libraries is now trivial
Since most of the app utilizes the abstract handlers, it's incredibly
easily to change to a different DB library.
Hypothetically, all that would need to be done is to simply replace the
the file containing the base handlers, while the rest of the app
would go unchanged.
- Syncing logic between Electron and web is now properly separated
There are now two distinct DB handling APIs: the Electron one and
the web one.
The app doesn't need to manually choose the API, because it's detected
which platform is being utilized on import.
- All Electron windows now share the same database instance
This provides a single source of truth, improving consistency
regarding data manipulation and windows synchronization.
As a sidenote, syncing implementation has been left as is
(web unimplemented; Electron only syncs settings, remaining
datastore syncing will be implemented in the upcoming commits).
* Electron/History: Implement history synchronization
* Profiles: Implement suplementary profile creation logic
* ft-profile-edit: Small fix on profile name missing display
* Electron/Profiles: Implement profile synchronization
* Electron/Playlists: Implement playlist synchronization
2021-12-15 18:42:24 +00:00
|
|
|
import { IpcChannels } from '../../../constants'
|
|
|
|
|
2020-02-16 18:30:00 +00:00
|
|
|
export default Vue.extend({
|
|
|
|
name: 'FtVideoPlayer',
|
|
|
|
components: {
|
|
|
|
'ft-card': FtCard
|
|
|
|
},
|
2020-10-04 18:30:54 +00:00
|
|
|
beforeRouteLeave: function () {
|
2020-12-22 18:58:17 +00:00
|
|
|
if (this.player !== null) {
|
|
|
|
this.exitFullWindow()
|
|
|
|
}
|
2020-10-04 18:30:54 +00:00
|
|
|
if (this.player !== null && !this.player.isInPictureInPicture()) {
|
|
|
|
this.player.dispose()
|
|
|
|
this.player = null
|
|
|
|
clearTimeout(this.mouseTimeout)
|
|
|
|
} else if (this.player.isInPictureInPicture()) {
|
|
|
|
this.player.play()
|
|
|
|
}
|
2021-01-13 20:56:25 +00:00
|
|
|
|
|
|
|
if (this.usingElectron && this.powerSaveBlocker !== null) {
|
2021-05-21 23:49:48 +00:00
|
|
|
const { ipcRenderer } = require('electron')
|
Store Revamp / Full database synchronization across windows (#1833)
* History: Refactor history module
* Profiles: Refactor profiles module
* IPC: Move channel ids to their own file and make them constants
* IPC: Replace single sync channel for one channel per sync type
* Everywhere: Replace default profile id magic strings with constant ref
* Profiles: Refactor `activeProfile` property from store
This commit makes it so that `activeProfile`'s getter returns
the entire profile, while the related update function only needs
the profile id (instead of the previously used array index)
to change the currently active profile.
This change was made due to inconsistency regarding the active profile
when creating new profiles.
If a new profile coincidentally landed in the current active profile's
array index after sorting, the app would mistakenly change to it
without any action from the user apart from the profile's creation.
Turning the profile id into the selector instead solves this issue.
* Revert "Store: Implement history synchronization between windows"
This reverts commit 99b61e617873412eb393d8f4dfccd8f8c172021f.
This is necessary for an upcoming improved implementation of the
history synchronization.
* History: Remove unused mutation
* Everywhere: Create abstract database handlers
The project now utilizes abstract handlers to fetch, modify
or otherwise manipulate data from the database.
This facilitates 3 aspects of the app, in addition of
making them future proof:
- Switching database libraries is now trivial
Since most of the app utilizes the abstract handlers, it's incredibly
easily to change to a different DB library.
Hypothetically, all that would need to be done is to simply replace the
the file containing the base handlers, while the rest of the app
would go unchanged.
- Syncing logic between Electron and web is now properly separated
There are now two distinct DB handling APIs: the Electron one and
the web one.
The app doesn't need to manually choose the API, because it's detected
which platform is being utilized on import.
- All Electron windows now share the same database instance
This provides a single source of truth, improving consistency
regarding data manipulation and windows synchronization.
As a sidenote, syncing implementation has been left as is
(web unimplemented; Electron only syncs settings, remaining
datastore syncing will be implemented in the upcoming commits).
* Electron/History: Implement history synchronization
* Profiles: Implement suplementary profile creation logic
* ft-profile-edit: Small fix on profile name missing display
* Electron/Profiles: Implement profile synchronization
* Electron/Playlists: Implement playlist synchronization
2021-12-15 18:42:24 +00:00
|
|
|
ipcRenderer.send(IpcChannels.STOP_POWER_SAVE_BLOCKER, this.powerSaveBlocker)
|
2021-01-13 20:56:25 +00:00
|
|
|
}
|
2020-10-04 18:30:54 +00:00
|
|
|
},
|
2020-02-16 18:30:00 +00:00
|
|
|
props: {
|
2020-02-19 03:31:10 +00:00
|
|
|
format: {
|
|
|
|
type: String,
|
|
|
|
required: true
|
|
|
|
},
|
2020-02-18 20:59:01 +00:00
|
|
|
sourceList: {
|
|
|
|
type: Array,
|
2020-04-21 20:22:41 +00:00
|
|
|
default: () => { return [] }
|
2020-02-18 20:59:01 +00:00
|
|
|
},
|
2021-05-15 19:08:41 +00:00
|
|
|
adaptiveFormats: {
|
|
|
|
type: Array,
|
|
|
|
default: () => { return [] }
|
|
|
|
},
|
2020-02-18 20:59:01 +00:00
|
|
|
dashSrc: {
|
2020-02-19 03:31:10 +00:00
|
|
|
type: Array,
|
2020-02-18 20:59:01 +00:00
|
|
|
default: null
|
|
|
|
},
|
|
|
|
hlsSrc: {
|
2020-02-19 03:31:10 +00:00
|
|
|
type: Array,
|
2020-02-18 20:59:01 +00:00
|
|
|
default: null
|
|
|
|
},
|
2021-03-17 01:28:25 +00:00
|
|
|
captionHybridList: {
|
2020-02-16 18:30:00 +00:00
|
|
|
type: Array,
|
|
|
|
default: () => { return [] }
|
|
|
|
},
|
2020-02-18 20:59:01 +00:00
|
|
|
storyboardSrc: {
|
2020-02-16 18:30:00 +00:00
|
|
|
type: String,
|
2020-02-18 20:59:01 +00:00
|
|
|
default: ''
|
2020-06-01 02:47:22 +00:00
|
|
|
},
|
|
|
|
thumbnail: {
|
|
|
|
type: String,
|
|
|
|
default: ''
|
2021-05-16 20:01:24 +00:00
|
|
|
},
|
|
|
|
videoId: {
|
|
|
|
type: String,
|
|
|
|
required: true
|
2020-02-16 18:30:00 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
data: function () {
|
|
|
|
return {
|
|
|
|
id: '',
|
2021-01-11 20:45:46 +00:00
|
|
|
powerSaveBlocker: null,
|
2020-03-01 03:37:02 +00:00
|
|
|
volume: 1,
|
2020-02-16 18:30:00 +00:00
|
|
|
player: null,
|
2020-02-18 20:59:01 +00:00
|
|
|
useDash: false,
|
|
|
|
useHls: false,
|
2020-09-25 02:35:13 +00:00
|
|
|
selectedDefaultQuality: '',
|
2021-03-12 21:09:08 +00:00
|
|
|
selectedQuality: '',
|
2022-02-19 22:17:58 +00:00
|
|
|
selectedResolution: '',
|
|
|
|
selectedBitrate: '',
|
|
|
|
selectedMimeType: '',
|
|
|
|
selectedFPS: 0,
|
2021-04-30 21:18:45 +00:00
|
|
|
using60Fps: false,
|
2020-11-13 22:29:41 +00:00
|
|
|
maxFramerate: 0,
|
2020-02-18 20:59:01 +00:00
|
|
|
activeSourceList: [],
|
2021-05-15 19:08:41 +00:00
|
|
|
activeAdaptiveFormats: [],
|
2020-02-21 18:31:32 +00:00
|
|
|
mouseTimeout: null,
|
2021-01-16 03:34:49 +00:00
|
|
|
touchTimeout: null,
|
|
|
|
lastTouchTime: null,
|
2022-02-19 22:17:58 +00:00
|
|
|
playerStats: null,
|
|
|
|
statsModal: null,
|
|
|
|
showStatsModal: false,
|
|
|
|
statsModalEventName: 'updateStats',
|
2020-02-16 18:30:00 +00:00
|
|
|
dataSetup: {
|
2021-01-15 04:20:42 +00:00
|
|
|
fluid: true,
|
2020-02-18 20:59:01 +00:00
|
|
|
nativeTextTracks: false,
|
|
|
|
plugins: {},
|
|
|
|
controlBar: {
|
|
|
|
children: [
|
|
|
|
'playToggle',
|
|
|
|
'volumePanel',
|
|
|
|
'currentTimeDisplay',
|
|
|
|
'timeDivider',
|
|
|
|
'durationDisplay',
|
|
|
|
'progressControl',
|
|
|
|
'liveDisplay',
|
|
|
|
'seekToLive',
|
|
|
|
'remainingTimeDisplay',
|
|
|
|
'customControlSpacer',
|
|
|
|
'playbackRateMenuButton',
|
2021-01-13 03:56:31 +00:00
|
|
|
'loopButton',
|
2020-02-18 20:59:01 +00:00
|
|
|
'chaptersButton',
|
|
|
|
'descriptionsButton',
|
|
|
|
'subsCapsButton',
|
2020-12-14 23:25:51 +00:00
|
|
|
'pictureInPictureToggle',
|
2021-08-24 07:36:10 +00:00
|
|
|
'toggleTheatreModeButton',
|
2020-12-14 23:25:51 +00:00
|
|
|
'fullWindowButton',
|
2020-12-14 22:37:58 +00:00
|
|
|
'qualitySelector',
|
2020-12-14 23:25:51 +00:00
|
|
|
'fullscreenToggle'
|
2020-02-18 20:59:01 +00:00
|
|
|
]
|
|
|
|
},
|
2020-02-16 18:30:00 +00:00
|
|
|
playbackRates: [
|
2020-12-17 16:22:05 +00:00
|
|
|
0.25,
|
2020-12-15 20:09:49 +00:00
|
|
|
0.5,
|
2020-12-17 16:22:05 +00:00
|
|
|
0.75,
|
|
|
|
1,
|
|
|
|
1.25,
|
|
|
|
1.5,
|
|
|
|
1.75,
|
|
|
|
2,
|
|
|
|
2.25,
|
|
|
|
2.5,
|
|
|
|
2.75,
|
2022-02-19 22:17:58 +00:00
|
|
|
3
|
2020-02-16 18:30:00 +00:00
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
computed: {
|
2021-01-11 20:45:46 +00:00
|
|
|
usingElectron: function () {
|
|
|
|
return this.$store.getters.getUsingElectron
|
|
|
|
},
|
|
|
|
|
2021-09-20 02:12:14 +00:00
|
|
|
currentLocale: function () {
|
|
|
|
return this.$store.getters.getCurrentLocale
|
|
|
|
},
|
|
|
|
|
2020-03-01 03:37:02 +00:00
|
|
|
defaultPlayback: function () {
|
|
|
|
return this.$store.getters.getDefaultPlayback
|
2020-02-20 20:58:21 +00:00
|
|
|
},
|
|
|
|
|
2021-08-05 20:17:01 +00:00
|
|
|
defaultSkipInterval: function () {
|
|
|
|
return this.$store.getters.getDefaultSkipInterval
|
|
|
|
},
|
|
|
|
|
2020-05-17 20:12:58 +00:00
|
|
|
defaultQuality: function () {
|
2020-09-26 16:10:56 +00:00
|
|
|
return parseInt(this.$store.getters.getDefaultQuality)
|
2020-05-17 20:12:58 +00:00
|
|
|
},
|
|
|
|
|
2021-06-10 19:35:00 +00:00
|
|
|
defaultCaptionSettings: function () {
|
|
|
|
try {
|
|
|
|
return JSON.parse(this.$store.getters.getDefaultCaptionSettings)
|
|
|
|
} catch (e) {
|
|
|
|
console.log(e)
|
|
|
|
return {}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2020-03-01 03:37:02 +00:00
|
|
|
defaultVideoFormat: function () {
|
|
|
|
return this.$store.getters.getDefaultVideoFormat
|
|
|
|
},
|
|
|
|
|
|
|
|
autoplayVideos: function () {
|
|
|
|
return this.$store.getters.getAutoplayVideos
|
2021-05-16 20:01:24 +00:00
|
|
|
},
|
|
|
|
|
2021-05-26 15:55:11 +00:00
|
|
|
videoVolumeMouseScroll: function () {
|
|
|
|
return this.$store.getters.getVideoVolumeMouseScroll
|
|
|
|
},
|
|
|
|
|
2021-11-24 21:52:56 +00:00
|
|
|
videoPlaybackRateMouseScroll: function () {
|
|
|
|
return this.$store.getters.getVideoPlaybackRateMouseScroll
|
|
|
|
},
|
|
|
|
|
2021-05-16 20:01:24 +00:00
|
|
|
useSponsorBlock: function () {
|
|
|
|
return this.$store.getters.getUseSponsorBlock
|
|
|
|
},
|
|
|
|
|
|
|
|
sponsorBlockShowSkippedToast: function () {
|
|
|
|
return this.$store.getters.getSponsorBlockShowSkippedToast
|
2021-05-29 18:35:28 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
displayVideoPlayButton: function() {
|
|
|
|
return this.$store.getters.getDisplayVideoPlayButton
|
Stats for nerds (#1867)
* transition duration of 0.5s added to watched videos
* small code reformating
* extra white spaces deleted
* typo in the word transition corrected
* original whitespaces respected
* transition added when hovering end
* video stat components started and properties chosen
* ft-video-stats integraded into the video player for dev and debugging
* using a timer to get video stats and a method to update the statistic every second
* getting statistic from vhs and adaptativeFormat
* frame drop capture
* stats capture in the form of event
* useless comment deleted
* stats render with a for loop in the template
* stats correctly displayed
* overlay stats added
* video stats component deleted
* video stats component deleted inside template video player
* video stats component fully deleted
* modal solution working need more styling and code messy
* lint
* modal working with stats
* keyboard shortcut for stats
* lint fix
* network state is now a string
* new line deleted
* useless whitespace deleted
* package-lock.json remove and ignore
* keyboard shortcut restricted to up arrow
* stats overlay made larger
* align to left corner
* useless formatting of string deleted
* renaming of variable formatedStrats for formattedStats
* keyboard shortcut made into a variable
* lint-fix
* key change for i
* label translated
* whitespace added for gitignore
* lock file not ignored
* videoId stat deleted
* ft-video-player.js, en-US.yaml, fr-FR.yaml: changing percentage stats display
changing the display for percentage stats for the format 'x%' instead of 'xx.xx'
* ft-video-player.js, en-US.yaml, fr-FR.yaml: network state video statistic deleted
* ft-video-player.js: made stats modal background color darker
* ft-video-player.js, en-US.yaml, fr-FR.yaml: video id are now related to the one of youtube
* ft-video-player.js, en-US.yaml, fr-FR.yaml: stats displayed made closet to the youtube implementation
the name are capitalized, the order of display is changed and fps is combined with viewport
* lint-fix
* en-US.yaml, fr-FR.yaml: network state possibilities deleted because not used
* package.json.lock: deleted
* ft-video-player.js: formated_stats renamed for formatted_stats
* lock file deleted
* index.js, ft-video-player.js: handling of right click context menu
via electon ipc bus an event is send to tell the vue component to show the stats modal
* ft-video-player.js, index.js: renaming of video stats display event and definition of it as a variable
* index.js, en-US.yaml: inconsistant capitalization of video statistics label solved
* index.js: pluralized video stats
* ft-video-player.js: fix right click undefined this.player
change the arrow function inside the closure for a function with a bind to this
* ft-video-player.js: handling of the case when this.player is not defined
the property this.stats.display.activated as been added and manage when the to show the stats. In this way in the runtime (it is still refered in the run time but it is capture in an event loop) with dont have to refer to this.player so when it is not defined it doesnt affect the behavior.
* lint fix
* src/renderer/components/ft-video-player/ft-video-player.js: modal.close move into the display event of the statistic context
* lint fix
* src/renderer/components/ft-video-player/ft-video-player.js, static/locales/en-US.yaml, static/locales/fr-FR.yaml: better capitalization of the stats labels
* static/locales/en-US.yaml: fps capitalized
* static/locales/fr-FR.yaml, static/locales/en-US.yaml: capitalized label
2021-11-23 11:34:04 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
watch: {
|
2022-02-19 22:17:58 +00:00
|
|
|
showStatsModal: function() {
|
|
|
|
this.player.trigger(this.statsModalEventName)
|
2020-02-16 18:30:00 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
mounted: function () {
|
|
|
|
this.id = this._uid
|
2020-02-18 20:59:01 +00:00
|
|
|
|
2020-03-01 03:37:02 +00:00
|
|
|
const volume = sessionStorage.getItem('volume')
|
|
|
|
|
|
|
|
if (volume !== null) {
|
|
|
|
this.volume = volume
|
|
|
|
}
|
|
|
|
|
2020-12-14 23:25:51 +00:00
|
|
|
this.createFullWindowButton()
|
2021-01-13 03:56:31 +00:00
|
|
|
this.createLoopButton()
|
2021-08-24 07:36:10 +00:00
|
|
|
this.createToggleTheatreModeButton()
|
2020-02-18 20:59:01 +00:00
|
|
|
this.determineFormatType()
|
2020-11-13 22:29:41 +00:00
|
|
|
this.determineMaxFramerate()
|
2020-02-16 18:30:00 +00:00
|
|
|
},
|
2020-02-20 20:58:21 +00:00
|
|
|
beforeDestroy: function () {
|
2020-12-22 18:58:17 +00:00
|
|
|
if (this.player !== null) {
|
|
|
|
this.exitFullWindow()
|
|
|
|
|
|
|
|
if (!this.player.isInPictureInPicture()) {
|
|
|
|
this.player.dispose()
|
|
|
|
this.player = null
|
|
|
|
clearTimeout(this.mouseTimeout)
|
|
|
|
}
|
2020-02-20 20:58:21 +00:00
|
|
|
}
|
2021-01-13 20:56:25 +00:00
|
|
|
|
|
|
|
if (this.usingElectron && this.powerSaveBlocker !== null) {
|
2021-05-21 23:49:48 +00:00
|
|
|
const { ipcRenderer } = require('electron')
|
Store Revamp / Full database synchronization across windows (#1833)
* History: Refactor history module
* Profiles: Refactor profiles module
* IPC: Move channel ids to their own file and make them constants
* IPC: Replace single sync channel for one channel per sync type
* Everywhere: Replace default profile id magic strings with constant ref
* Profiles: Refactor `activeProfile` property from store
This commit makes it so that `activeProfile`'s getter returns
the entire profile, while the related update function only needs
the profile id (instead of the previously used array index)
to change the currently active profile.
This change was made due to inconsistency regarding the active profile
when creating new profiles.
If a new profile coincidentally landed in the current active profile's
array index after sorting, the app would mistakenly change to it
without any action from the user apart from the profile's creation.
Turning the profile id into the selector instead solves this issue.
* Revert "Store: Implement history synchronization between windows"
This reverts commit 99b61e617873412eb393d8f4dfccd8f8c172021f.
This is necessary for an upcoming improved implementation of the
history synchronization.
* History: Remove unused mutation
* Everywhere: Create abstract database handlers
The project now utilizes abstract handlers to fetch, modify
or otherwise manipulate data from the database.
This facilitates 3 aspects of the app, in addition of
making them future proof:
- Switching database libraries is now trivial
Since most of the app utilizes the abstract handlers, it's incredibly
easily to change to a different DB library.
Hypothetically, all that would need to be done is to simply replace the
the file containing the base handlers, while the rest of the app
would go unchanged.
- Syncing logic between Electron and web is now properly separated
There are now two distinct DB handling APIs: the Electron one and
the web one.
The app doesn't need to manually choose the API, because it's detected
which platform is being utilized on import.
- All Electron windows now share the same database instance
This provides a single source of truth, improving consistency
regarding data manipulation and windows synchronization.
As a sidenote, syncing implementation has been left as is
(web unimplemented; Electron only syncs settings, remaining
datastore syncing will be implemented in the upcoming commits).
* Electron/History: Implement history synchronization
* Profiles: Implement suplementary profile creation logic
* ft-profile-edit: Small fix on profile name missing display
* Electron/Profiles: Implement profile synchronization
* Electron/Playlists: Implement playlist synchronization
2021-12-15 18:42:24 +00:00
|
|
|
ipcRenderer.send(IpcChannels.STOP_POWER_SAVE_BLOCKER, this.powerSaveBlocker)
|
2021-01-13 20:56:25 +00:00
|
|
|
}
|
2020-02-20 20:58:21 +00:00
|
|
|
},
|
2020-02-16 18:30:00 +00:00
|
|
|
methods: {
|
2020-09-25 02:35:13 +00:00
|
|
|
initializePlayer: async function () {
|
2021-05-15 19:08:41 +00:00
|
|
|
console.log(this.adaptiveFormats)
|
2020-02-16 18:30:00 +00:00
|
|
|
const videoPlayer = document.getElementById(this.id)
|
|
|
|
if (videoPlayer !== null) {
|
2020-05-24 21:32:02 +00:00
|
|
|
if (!this.useDash) {
|
2020-02-18 20:59:01 +00:00
|
|
|
qualitySelector(videojs, { showQualitySelectionLabelInControlBar: true })
|
2020-09-26 21:03:42 +00:00
|
|
|
await this.determineDefaultQualityLegacy()
|
2020-02-18 20:59:01 +00:00
|
|
|
}
|
|
|
|
|
2020-10-28 21:20:07 +00:00
|
|
|
this.player = videojs(videoPlayer, {
|
|
|
|
html5: {
|
2021-03-17 01:28:25 +00:00
|
|
|
preloadTextTracks: false,
|
2020-10-28 21:20:07 +00:00
|
|
|
vhs: {
|
2020-12-27 03:21:06 +00:00
|
|
|
limitRenditionByPlayerDimensions: false,
|
|
|
|
smoothQualityChange: false,
|
2021-03-12 21:09:08 +00:00
|
|
|
allowSeeksWithinUnsafeLiveWindow: true,
|
|
|
|
handlePartialData: true
|
2020-10-28 21:20:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2020-12-14 23:25:51 +00:00
|
|
|
|
2020-03-01 03:37:02 +00:00
|
|
|
this.player.volume(this.volume)
|
|
|
|
this.player.playbackRate(this.defaultPlayback)
|
2021-06-10 19:35:00 +00:00
|
|
|
this.player.textTrackSettings.setValues(this.defaultCaptionSettings)
|
2021-05-28 20:29:35 +00:00
|
|
|
// Remove big play button
|
|
|
|
// https://github.com/videojs/video.js/blob/v7.12.1/docs/guides/components.md#basic-example
|
2021-05-29 18:35:28 +00:00
|
|
|
if (!this.displayVideoPlayButton) {
|
|
|
|
this.player.removeChild('BigPlayButton')
|
|
|
|
}
|
2020-03-01 03:37:02 +00:00
|
|
|
|
2021-11-30 22:08:33 +00:00
|
|
|
// Makes the playback rate menu focus the current item on mouse hover
|
|
|
|
// or the closest item if the playback rate is between two items
|
|
|
|
// which is likely to be the case when the playback rate is changed by scrolling
|
|
|
|
const playbackRateMenuButton = this.player.controlBar.getChild('playbackRateMenuButton')
|
|
|
|
playbackRateMenuButton.on(playbackRateMenuButton.menuButton_, 'mouseenter', () => {
|
|
|
|
const playbackRate = this.player.playbackRate()
|
|
|
|
const rates = this.player.playbackRates()
|
|
|
|
|
|
|
|
// iterate through the items in reverse order as the highest is displayed first
|
|
|
|
// `slice` must be used as `reverse` does reversing in place
|
|
|
|
const targetPlaybackRateMenuItemIndex = rates.slice().reverse().findIndex((rate) => {
|
|
|
|
return rate === playbackRate || rate < playbackRate
|
|
|
|
})
|
|
|
|
|
|
|
|
// center the selected item in the middle of the visible area
|
|
|
|
// the first and last items will never be in the center so it can be skipped for them
|
|
|
|
if (targetPlaybackRateMenuItemIndex !== 0 && targetPlaybackRateMenuItemIndex !== rates.length - 1) {
|
|
|
|
const playbackRateMenu = playbackRateMenuButton.menu
|
|
|
|
const menuElement = playbackRateMenu.contentEl()
|
|
|
|
|
|
|
|
const itemHeight = playbackRateMenu.children()[targetPlaybackRateMenuItemIndex].contentEl().clientHeight
|
|
|
|
|
|
|
|
// clientHeight is the height of the visible part of an element
|
|
|
|
const centerOfVisibleArea = (menuElement.clientHeight - itemHeight) / 2
|
|
|
|
const menuScrollOffset = (itemHeight * targetPlaybackRateMenuItemIndex) - centerOfVisibleArea
|
|
|
|
|
|
|
|
menuElement.scrollTo({ top: menuScrollOffset })
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2020-09-25 02:35:13 +00:00
|
|
|
if (this.storyboardSrc !== '') {
|
|
|
|
this.player.vttThumbnails({
|
|
|
|
src: this.storyboardSrc,
|
|
|
|
showTimestamp: true
|
|
|
|
})
|
|
|
|
}
|
2020-02-18 20:59:01 +00:00
|
|
|
|
2020-05-24 21:32:02 +00:00
|
|
|
if (this.useDash) {
|
2021-03-12 21:09:08 +00:00
|
|
|
// this.dataSetup.plugins.httpSourceSelector = {
|
|
|
|
// default: 'auto'
|
|
|
|
// }
|
2020-02-18 20:59:01 +00:00
|
|
|
|
2021-03-12 21:09:08 +00:00
|
|
|
// this.player.httpSourceSelector()
|
|
|
|
this.createDashQualitySelector(this.player.qualityLevels())
|
2020-02-18 20:59:01 +00:00
|
|
|
}
|
2020-02-20 20:58:21 +00:00
|
|
|
|
2020-03-01 03:37:02 +00:00
|
|
|
if (this.autoplayVideos) {
|
2020-02-20 20:58:21 +00:00
|
|
|
// Calling play() won't happen right away, so a quick timeout will make it function properly.
|
|
|
|
setTimeout(() => {
|
|
|
|
this.player.play()
|
2020-05-24 21:32:02 +00:00
|
|
|
}, 200)
|
2020-02-20 20:58:21 +00:00
|
|
|
}
|
|
|
|
|
2021-08-24 22:46:42 +00:00
|
|
|
// Remove built-in progress bar mouse over current time display
|
|
|
|
// `MouseTimeDisplay` in
|
|
|
|
// https://github.com/videojs/video.js/blob/v7.13.3/docs/guides/components.md#default-component-tree
|
|
|
|
this.player.controlBar.progressControl.seekBar.playProgressBar.removeChild('timeTooltip')
|
|
|
|
|
2021-05-20 03:00:39 +00:00
|
|
|
if (this.useSponsorBlock) {
|
|
|
|
this.initializeSponsorBlock()
|
|
|
|
}
|
|
|
|
|
2020-12-14 23:25:51 +00:00
|
|
|
$(document).on('keydown', this.keyboardShortcutHandler)
|
2020-02-21 18:31:32 +00:00
|
|
|
|
|
|
|
this.player.on('mousemove', this.hideMouseTimeout)
|
|
|
|
this.player.on('mouseleave', this.removeMouseTimeout)
|
2020-10-27 19:20:41 +00:00
|
|
|
|
2020-03-01 03:37:02 +00:00
|
|
|
this.player.on('volumechange', this.updateVolume)
|
2021-05-26 15:55:11 +00:00
|
|
|
if (this.videoVolumeMouseScroll) {
|
|
|
|
this.player.on('wheel', this.mouseScrollVolume)
|
|
|
|
} else {
|
|
|
|
this.player.controlBar.getChild('volumePanel').on('wheel', this.mouseScrollVolume)
|
|
|
|
}
|
2020-02-21 18:31:32 +00:00
|
|
|
|
2021-11-24 21:52:56 +00:00
|
|
|
if (this.videoPlaybackRateMouseScroll) {
|
|
|
|
this.player.on('wheel', this.mouseScrollPlaybackRate)
|
|
|
|
// Removes the 'out-of-the-box' click event and adds a custom click event so that a user can
|
|
|
|
// ctrl-click (or command+click on a mac) without toggling play/pause
|
|
|
|
this.player.el_.firstChild.style.pointerEvents = 'none'
|
|
|
|
this.player.on('click', this.handlePlayerClick)
|
|
|
|
}
|
|
|
|
|
2020-11-15 19:13:18 +00:00
|
|
|
this.player.on('fullscreenchange', this.fullscreenOverlay)
|
2021-04-22 18:41:50 +00:00
|
|
|
this.player.on('fullscreenchange', this.toggleFullscreenClass)
|
2020-11-15 19:13:18 +00:00
|
|
|
|
2021-05-21 23:52:11 +00:00
|
|
|
this.player.on('ready', () => {
|
|
|
|
this.$emit('ready')
|
|
|
|
this.checkAspectRatio()
|
2022-02-19 22:17:58 +00:00
|
|
|
this.createStatsModal()
|
2021-05-21 23:52:11 +00:00
|
|
|
if (this.captionHybridList.length !== 0) {
|
|
|
|
this.transformAndInsertCaptions()
|
2021-03-17 01:28:25 +00:00
|
|
|
}
|
2020-08-20 02:39:44 +00:00
|
|
|
})
|
|
|
|
|
2021-05-21 23:52:11 +00:00
|
|
|
this.player.on('ended', () => {
|
|
|
|
this.$emit('ended')
|
2020-05-17 20:12:58 +00:00
|
|
|
})
|
|
|
|
|
2021-05-21 23:52:11 +00:00
|
|
|
this.player.on('error', (error, message) => {
|
|
|
|
this.$emit('error', error.target.player.error_)
|
2020-02-21 18:31:32 +00:00
|
|
|
})
|
2021-01-11 20:45:46 +00:00
|
|
|
|
2021-05-21 23:49:48 +00:00
|
|
|
this.player.on('play', async function () {
|
2021-01-11 20:45:46 +00:00
|
|
|
if (this.usingElectron) {
|
2021-05-21 23:49:48 +00:00
|
|
|
const { ipcRenderer } = require('electron')
|
|
|
|
this.powerSaveBlocker =
|
Store Revamp / Full database synchronization across windows (#1833)
* History: Refactor history module
* Profiles: Refactor profiles module
* IPC: Move channel ids to their own file and make them constants
* IPC: Replace single sync channel for one channel per sync type
* Everywhere: Replace default profile id magic strings with constant ref
* Profiles: Refactor `activeProfile` property from store
This commit makes it so that `activeProfile`'s getter returns
the entire profile, while the related update function only needs
the profile id (instead of the previously used array index)
to change the currently active profile.
This change was made due to inconsistency regarding the active profile
when creating new profiles.
If a new profile coincidentally landed in the current active profile's
array index after sorting, the app would mistakenly change to it
without any action from the user apart from the profile's creation.
Turning the profile id into the selector instead solves this issue.
* Revert "Store: Implement history synchronization between windows"
This reverts commit 99b61e617873412eb393d8f4dfccd8f8c172021f.
This is necessary for an upcoming improved implementation of the
history synchronization.
* History: Remove unused mutation
* Everywhere: Create abstract database handlers
The project now utilizes abstract handlers to fetch, modify
or otherwise manipulate data from the database.
This facilitates 3 aspects of the app, in addition of
making them future proof:
- Switching database libraries is now trivial
Since most of the app utilizes the abstract handlers, it's incredibly
easily to change to a different DB library.
Hypothetically, all that would need to be done is to simply replace the
the file containing the base handlers, while the rest of the app
would go unchanged.
- Syncing logic between Electron and web is now properly separated
There are now two distinct DB handling APIs: the Electron one and
the web one.
The app doesn't need to manually choose the API, because it's detected
which platform is being utilized on import.
- All Electron windows now share the same database instance
This provides a single source of truth, improving consistency
regarding data manipulation and windows synchronization.
As a sidenote, syncing implementation has been left as is
(web unimplemented; Electron only syncs settings, remaining
datastore syncing will be implemented in the upcoming commits).
* Electron/History: Implement history synchronization
* Profiles: Implement suplementary profile creation logic
* ft-profile-edit: Small fix on profile name missing display
* Electron/Profiles: Implement profile synchronization
* Electron/Playlists: Implement playlist synchronization
2021-12-15 18:42:24 +00:00
|
|
|
await ipcRenderer.invoke(IpcChannels.START_POWER_SAVE_BLOCKER)
|
2021-01-11 20:45:46 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
this.player.on('pause', function () {
|
|
|
|
if (this.usingElectron && this.powerSaveBlocker !== null) {
|
2021-05-21 23:49:48 +00:00
|
|
|
const { ipcRenderer } = require('electron')
|
Store Revamp / Full database synchronization across windows (#1833)
* History: Refactor history module
* Profiles: Refactor profiles module
* IPC: Move channel ids to their own file and make them constants
* IPC: Replace single sync channel for one channel per sync type
* Everywhere: Replace default profile id magic strings with constant ref
* Profiles: Refactor `activeProfile` property from store
This commit makes it so that `activeProfile`'s getter returns
the entire profile, while the related update function only needs
the profile id (instead of the previously used array index)
to change the currently active profile.
This change was made due to inconsistency regarding the active profile
when creating new profiles.
If a new profile coincidentally landed in the current active profile's
array index after sorting, the app would mistakenly change to it
without any action from the user apart from the profile's creation.
Turning the profile id into the selector instead solves this issue.
* Revert "Store: Implement history synchronization between windows"
This reverts commit 99b61e617873412eb393d8f4dfccd8f8c172021f.
This is necessary for an upcoming improved implementation of the
history synchronization.
* History: Remove unused mutation
* Everywhere: Create abstract database handlers
The project now utilizes abstract handlers to fetch, modify
or otherwise manipulate data from the database.
This facilitates 3 aspects of the app, in addition of
making them future proof:
- Switching database libraries is now trivial
Since most of the app utilizes the abstract handlers, it's incredibly
easily to change to a different DB library.
Hypothetically, all that would need to be done is to simply replace the
the file containing the base handlers, while the rest of the app
would go unchanged.
- Syncing logic between Electron and web is now properly separated
There are now two distinct DB handling APIs: the Electron one and
the web one.
The app doesn't need to manually choose the API, because it's detected
which platform is being utilized on import.
- All Electron windows now share the same database instance
This provides a single source of truth, improving consistency
regarding data manipulation and windows synchronization.
As a sidenote, syncing implementation has been left as is
(web unimplemented; Electron only syncs settings, remaining
datastore syncing will be implemented in the upcoming commits).
* Electron/History: Implement history synchronization
* Profiles: Implement suplementary profile creation logic
* ft-profile-edit: Small fix on profile name missing display
* Electron/Profiles: Implement profile synchronization
* Electron/Playlists: Implement playlist synchronization
2021-12-15 18:42:24 +00:00
|
|
|
ipcRenderer.send(IpcChannels.STOP_POWER_SAVE_BLOCKER, this.powerSaveBlocker)
|
2021-01-11 20:45:46 +00:00
|
|
|
this.powerSaveBlocker = null
|
|
|
|
}
|
|
|
|
})
|
2021-06-10 19:35:00 +00:00
|
|
|
|
2022-02-19 22:17:58 +00:00
|
|
|
this.player.on(this.statsModalEventName, () => {
|
|
|
|
if (this.showStatsModal) {
|
|
|
|
this.statsModal.open()
|
|
|
|
this.player.controls(true)
|
|
|
|
this.statsModal.contentEl().innerHTML = this.getFormattedStats()
|
|
|
|
} else {
|
|
|
|
this.statsModal.close()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
this.player.on('timeupdate', () => {
|
|
|
|
if (this.format === 'dash') {
|
|
|
|
this.playerStats = this.player.tech({ IWillNotUseThisInPlugins: true }).vhs.stats
|
|
|
|
this.updateStatsContent()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2021-06-10 19:35:00 +00:00
|
|
|
this.player.textTrackSettings.on('modalclose', (_) => {
|
|
|
|
const settings = this.player.textTrackSettings.getValues()
|
|
|
|
this.updateDefaultCaptionSettings(JSON.stringify(settings))
|
|
|
|
})
|
2022-02-19 22:17:58 +00:00
|
|
|
|
|
|
|
// right click menu
|
|
|
|
if (this.usingElectron) {
|
|
|
|
const { ipcRenderer } = require('electron')
|
|
|
|
ipcRenderer.removeAllListeners('showVideoStatistics')
|
|
|
|
ipcRenderer.on('showVideoStatistics', (event) => {
|
|
|
|
this.toggleShowStatsModal()
|
|
|
|
})
|
|
|
|
}
|
2020-02-16 18:30:00 +00:00
|
|
|
}
|
2021-05-16 20:01:24 +00:00
|
|
|
},
|
|
|
|
|
2021-05-20 03:00:39 +00:00
|
|
|
initializeSponsorBlock() {
|
2021-05-21 23:56:32 +00:00
|
|
|
this.sponsorBlockSkipSegments({
|
2021-05-20 03:00:39 +00:00
|
|
|
videoId: this.videoId,
|
|
|
|
categories: ['sponsor']
|
|
|
|
}).then((skipSegments) => {
|
|
|
|
if (skipSegments.length === 0) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
this.player.ready(() => {
|
2021-05-16 20:01:24 +00:00
|
|
|
this.player.on('timeupdate', () => {
|
|
|
|
this.skipSponsorBlocks(skipSegments)
|
|
|
|
})
|
2021-05-20 03:00:39 +00:00
|
|
|
|
2021-05-16 20:01:24 +00:00
|
|
|
skipSegments.forEach(({
|
|
|
|
category,
|
|
|
|
segment: [startTime, endTime]
|
|
|
|
}) => {
|
|
|
|
this.addSponsorBlockMarker({
|
|
|
|
time: startTime,
|
|
|
|
duration: endTime - startTime,
|
|
|
|
color: this.sponsorBlockCategoryColor(category)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
2021-05-20 03:00:39 +00:00
|
|
|
})
|
2021-05-16 20:01:24 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
skipSponsorBlocks(skipSegments) {
|
|
|
|
const currentTime = this.player.currentTime()
|
2021-05-20 03:00:39 +00:00
|
|
|
const duration = this.player.duration()
|
2021-05-16 20:01:24 +00:00
|
|
|
let newTime = null
|
|
|
|
let skippedCategory = null
|
|
|
|
skipSegments.forEach(({ category, segment: [startTime, endTime] }) => {
|
|
|
|
if (startTime <= currentTime && currentTime < endTime) {
|
|
|
|
newTime = endTime
|
|
|
|
skippedCategory = category
|
|
|
|
}
|
|
|
|
})
|
2021-05-20 03:00:39 +00:00
|
|
|
if (newTime !== null && Math.abs(duration - currentTime) > 0.500) {
|
2021-05-16 20:01:24 +00:00
|
|
|
if (this.sponsorBlockShowSkippedToast) {
|
|
|
|
this.showSkippedSponsorSegmentInformation(skippedCategory)
|
|
|
|
}
|
|
|
|
this.player.currentTime(newTime)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
showSkippedSponsorSegmentInformation(category) {
|
|
|
|
const translatedCategory = this.sponsorBlockTranslatedCategory(category)
|
|
|
|
this.showToast({
|
|
|
|
message: `${this.$t('Video.Skipped segment')} ${translatedCategory}`
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
sponsorBlockTranslatedCategory(category) {
|
|
|
|
switch (category) {
|
|
|
|
case 'sponsor':
|
|
|
|
return this.$t('Video.Sponsor Block category.sponsor')
|
|
|
|
case 'intro':
|
|
|
|
return this.$t('Video.Sponsor Block category.intro')
|
|
|
|
case 'outro':
|
|
|
|
return this.$t('Video.Sponsor Block category.outro')
|
|
|
|
case 'selfpromo':
|
|
|
|
return this.$t('Video.Sponsor Block category.self-promotion')
|
|
|
|
case 'interaction':
|
|
|
|
return this.$t('Video.Sponsor Block category.interaction')
|
|
|
|
case 'music_offtopic':
|
|
|
|
return this.$t('Video.Sponsor Block category.music offtopic')
|
|
|
|
default:
|
|
|
|
console.error(`Unknown translation for SponsorBlock category ${category}`)
|
|
|
|
return category
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
sponsorBlockCategoryColor(category) {
|
|
|
|
// TODO: allow to set these colors in settings
|
|
|
|
switch (category) {
|
|
|
|
case 'sponsor':
|
2021-05-17 19:05:42 +00:00
|
|
|
return 'var(--accent-color)'
|
2021-05-16 20:01:24 +00:00
|
|
|
case 'intro':
|
2021-05-17 19:05:42 +00:00
|
|
|
return 'var(--accent-color)'
|
2021-05-16 20:01:24 +00:00
|
|
|
case 'outro':
|
2021-05-17 19:05:42 +00:00
|
|
|
return 'var(--accent-color)'
|
2021-05-16 20:01:24 +00:00
|
|
|
case 'selfpromo':
|
2021-05-17 19:05:42 +00:00
|
|
|
return 'var(--accent-color)'
|
2021-05-16 20:01:24 +00:00
|
|
|
case 'interaction':
|
2021-05-17 19:05:42 +00:00
|
|
|
return 'var(--accent-color)'
|
2021-05-16 20:01:24 +00:00
|
|
|
case 'music_offtopic':
|
2021-05-17 19:05:42 +00:00
|
|
|
return 'var(--accent-color)'
|
2021-05-16 20:01:24 +00:00
|
|
|
default:
|
|
|
|
console.error(`Unknown SponsorBlock category ${category}`)
|
2021-05-17 19:06:10 +00:00
|
|
|
return 'var(--accent-color)'
|
2021-05-16 20:01:24 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
addSponsorBlockMarker(marker) {
|
|
|
|
const markerDiv = videojs.dom.createEl('div', {}, {})
|
|
|
|
|
|
|
|
markerDiv.className = 'sponsorBlockMarker'
|
|
|
|
markerDiv.style.height = '100%'
|
|
|
|
markerDiv.style.position = 'absolute'
|
|
|
|
markerDiv.style['background-color'] = marker.color
|
|
|
|
markerDiv.style.width = (marker.duration / this.player.duration()) * 100 + '%'
|
|
|
|
markerDiv.style.marginLeft = (marker.time / this.player.duration()) * 100 + '%'
|
|
|
|
|
|
|
|
this.player.el().querySelector('.vjs-progress-holder').appendChild(markerDiv)
|
2020-02-18 20:59:01 +00:00
|
|
|
},
|
|
|
|
|
2021-01-15 04:20:42 +00:00
|
|
|
checkAspectRatio() {
|
|
|
|
const videoWidth = this.player.videoWidth()
|
|
|
|
const videoHeight = this.player.videoHeight()
|
|
|
|
|
|
|
|
if (videoWidth === 0 || videoHeight === 0) {
|
|
|
|
setTimeout(() => {
|
|
|
|
this.checkAspectRatio()
|
|
|
|
}, 200)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-01-21 15:28:45 +00:00
|
|
|
if ((videoWidth - videoHeight) <= 240) {
|
2021-01-15 04:20:42 +00:00
|
|
|
this.player.fluid(false)
|
|
|
|
this.player.aspectRatio('16:9')
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2021-06-14 17:15:46 +00:00
|
|
|
updateVolume: function (_event) {
|
|
|
|
// 0 means muted
|
|
|
|
// https://docs.videojs.com/html5#volume
|
|
|
|
const volume = this.player.muted() ? 0 : this.player.volume()
|
2020-03-01 03:37:02 +00:00
|
|
|
sessionStorage.setItem('volume', volume)
|
|
|
|
},
|
|
|
|
|
2020-10-27 19:20:41 +00:00
|
|
|
mouseScrollVolume: function (event) {
|
2021-09-05 19:39:44 +00:00
|
|
|
if (event.target && !event.currentTarget.querySelector('.vjs-menu:hover')) {
|
2020-10-27 19:20:41 +00:00
|
|
|
event.preventDefault()
|
|
|
|
|
|
|
|
if (this.player.muted() && event.wheelDelta > 0) {
|
|
|
|
this.player.muted(false)
|
|
|
|
this.player.volume(0)
|
|
|
|
}
|
|
|
|
|
2021-11-24 21:52:56 +00:00
|
|
|
if (!event.ctrlKey && !event.metaKey) {
|
|
|
|
if (!this.player.muted()) {
|
|
|
|
if (event.wheelDelta > 0) {
|
|
|
|
this.changeVolume(0.05)
|
|
|
|
} else if (event.wheelDelta < 0) {
|
|
|
|
this.changeVolume(-0.05)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
mouseScrollPlaybackRate: function (event) {
|
|
|
|
if (event.target && !event.currentTarget.querySelector('.vjs-menu:hover')) {
|
|
|
|
event.preventDefault()
|
|
|
|
|
|
|
|
if (event.ctrlKey || event.metaKey) {
|
2020-10-27 19:20:41 +00:00
|
|
|
if (event.wheelDelta > 0) {
|
2021-11-24 21:52:56 +00:00
|
|
|
this.changePlayBackRate(0.05)
|
2020-10-27 19:20:41 +00:00
|
|
|
} else if (event.wheelDelta < 0) {
|
2021-11-24 21:52:56 +00:00
|
|
|
this.changePlayBackRate(-0.05)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
handlePlayerClick: function (event) {
|
|
|
|
if (event.target.matches('.ftVideoPlayer')) {
|
|
|
|
if (event.ctrlKey || event.metaKey) {
|
|
|
|
this.player.playbackRate(this.defaultPlayback)
|
|
|
|
} else {
|
|
|
|
if (this.player.paused() || !this.player.hasStarted()) {
|
|
|
|
this.player.play()
|
|
|
|
} else {
|
|
|
|
this.player.pause()
|
2020-10-27 19:20:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2020-02-18 20:59:01 +00:00
|
|
|
determineFormatType: function () {
|
2020-02-19 03:31:10 +00:00
|
|
|
if (this.format === 'dash') {
|
2020-02-18 20:59:01 +00:00
|
|
|
this.enableDashFormat()
|
|
|
|
} else {
|
|
|
|
this.enableLegacyFormat()
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2020-11-13 22:29:41 +00:00
|
|
|
determineMaxFramerate: function() {
|
2020-11-15 18:46:18 +00:00
|
|
|
if (this.dashSrc.length === 0) {
|
|
|
|
this.maxFramerate = 60
|
|
|
|
return
|
|
|
|
}
|
2020-11-13 22:29:41 +00:00
|
|
|
fs.readFile(this.dashSrc[0].url, (err, data) => {
|
|
|
|
if (err) {
|
|
|
|
this.maxFramerate = 60
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (data.includes('frameRate="60"')) {
|
|
|
|
this.maxFramerate = 60
|
|
|
|
} else {
|
|
|
|
this.maxFramerate = 30
|
|
|
|
}
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2020-09-26 21:03:42 +00:00
|
|
|
determineDefaultQualityLegacy: function () {
|
2020-05-24 21:46:26 +00:00
|
|
|
if (this.useDash) {
|
2020-09-25 02:35:13 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.sourceList.length === 0) {
|
|
|
|
return ''
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof (this.sourceList[0].qualityLabel) === 'number') {
|
|
|
|
return ''
|
2020-05-24 21:46:26 +00:00
|
|
|
}
|
|
|
|
|
2020-10-05 01:20:30 +00:00
|
|
|
if (this.sourceList[this.sourceList.length - 1].qualityLabel === this.$t('Video.Audio.Low')) {
|
|
|
|
this.selectedDefaultQuality = this.sourceList[0].qualityLabel
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-09-26 21:03:42 +00:00
|
|
|
let defaultQuality = this.defaultQuality
|
|
|
|
|
|
|
|
if (defaultQuality === 'auto') {
|
|
|
|
defaultQuality = 720
|
|
|
|
}
|
|
|
|
|
2020-09-25 02:35:13 +00:00
|
|
|
let maxAvailableQuality = parseInt(this.sourceList[this.sourceList.length - 1].qualityLabel.replace(/p|k/, ''))
|
|
|
|
|
|
|
|
if (maxAvailableQuality === 4) {
|
|
|
|
maxAvailableQuality = 2160
|
|
|
|
}
|
|
|
|
|
|
|
|
if (maxAvailableQuality === 8) {
|
|
|
|
maxAvailableQuality = 4320
|
|
|
|
}
|
|
|
|
|
2020-09-26 21:03:42 +00:00
|
|
|
if (maxAvailableQuality < defaultQuality) {
|
2020-09-25 02:35:13 +00:00
|
|
|
this.selectedDefaultQuality = this.sourceList[this.sourceList.length - 1].qualityLabel
|
|
|
|
}
|
|
|
|
|
|
|
|
const reversedList = [].concat(this.sourceList).reverse()
|
|
|
|
|
|
|
|
reversedList.forEach((source, index) => {
|
|
|
|
let qualityNumber = parseInt(source.qualityLabel.replace(/p|k/, ''))
|
|
|
|
if (qualityNumber === 4) {
|
|
|
|
qualityNumber = 2160
|
2020-05-17 20:12:58 +00:00
|
|
|
}
|
2020-09-25 02:35:13 +00:00
|
|
|
if (qualityNumber === 8) {
|
|
|
|
qualityNumber = 4320
|
|
|
|
}
|
|
|
|
|
2020-09-26 21:03:42 +00:00
|
|
|
if (defaultQuality === qualityNumber) {
|
2020-09-26 16:10:56 +00:00
|
|
|
this.selectedDefaultQuality = source.qualityLabel
|
|
|
|
}
|
|
|
|
|
2020-09-25 02:35:13 +00:00
|
|
|
if (index < (this.sourceList.length - 1)) {
|
|
|
|
let upperQualityNumber = parseInt(reversedList[index + 1].qualityLabel.replace(/p|k/, ''))
|
|
|
|
if (upperQualityNumber === 4) {
|
|
|
|
upperQualityNumber = 2160
|
|
|
|
}
|
|
|
|
if (upperQualityNumber === 8) {
|
|
|
|
upperQualityNumber = 4320
|
|
|
|
}
|
2020-09-26 21:03:42 +00:00
|
|
|
if (defaultQuality >= qualityNumber && defaultQuality < upperQualityNumber) {
|
2020-09-25 02:35:13 +00:00
|
|
|
this.selectedDefaultQuality = source.qualityLabel
|
|
|
|
}
|
2020-09-26 21:03:42 +00:00
|
|
|
} else if (qualityNumber <= defaultQuality) {
|
2020-09-25 02:35:13 +00:00
|
|
|
this.selectedDefaultQuality = source.qualityLabel
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
if (this.selectedDefaultQuality === '') {
|
|
|
|
this.selectedDefaultQuality = this.sourceList[this.sourceList.length - 1].qualityLabel
|
2020-05-17 20:12:58 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2020-09-26 21:03:42 +00:00
|
|
|
determineDefaultQualityDash: function () {
|
|
|
|
if (this.defaultQuality === 'auto') {
|
2021-03-12 21:09:08 +00:00
|
|
|
this.setDashQualityLevel('auto')
|
2020-09-26 21:03:42 +00:00
|
|
|
}
|
|
|
|
|
2021-05-17 01:40:34 +00:00
|
|
|
let formatsToTest
|
2021-05-15 19:08:41 +00:00
|
|
|
|
2021-05-17 01:40:34 +00:00
|
|
|
if (typeof this.activeAdaptiveFormats !== 'undefined' && this.activeAdaptiveFormats.length > 0) {
|
2021-05-15 19:08:41 +00:00
|
|
|
formatsToTest = this.activeAdaptiveFormats.filter((format) => {
|
2021-05-17 01:40:34 +00:00
|
|
|
return format.height === this.defaultQuality
|
2021-05-15 19:08:41 +00:00
|
|
|
})
|
|
|
|
|
2021-05-17 01:40:34 +00:00
|
|
|
if (formatsToTest.length === 0) {
|
|
|
|
formatsToTest = this.activeAdaptiveFormats.filter((format) => {
|
|
|
|
return format.height < this.defaultQuality
|
|
|
|
})
|
2021-05-15 19:08:41 +00:00
|
|
|
}
|
2021-05-17 01:40:34 +00:00
|
|
|
|
|
|
|
formatsToTest = formatsToTest.sort((a, b) => {
|
|
|
|
if (a.height === b.height) {
|
|
|
|
return b.bitrate - a.bitrate
|
|
|
|
} else {
|
|
|
|
return b.height - a.height
|
|
|
|
}
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
formatsToTest = this.player.qualityLevels().levels_.filter((format) => {
|
|
|
|
return format.height === this.defaultQuality
|
|
|
|
})
|
|
|
|
|
|
|
|
if (formatsToTest.length === 0) {
|
|
|
|
formatsToTest = this.player.qualityLevels().levels_.filter((format) => {
|
|
|
|
return format.height < this.defaultQuality
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
formatsToTest = formatsToTest.sort((a, b) => {
|
|
|
|
if (a.height === b.height) {
|
|
|
|
return b.bitrate - a.bitrate
|
|
|
|
} else {
|
|
|
|
return b.height - a.height
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2021-05-15 19:08:41 +00:00
|
|
|
|
2021-05-15 19:13:17 +00:00
|
|
|
// TODO: Test formats to determine if HDR / 60 FPS and skip them based on
|
|
|
|
// User settings
|
2021-05-15 19:08:41 +00:00
|
|
|
this.setDashQualityLevel(formatsToTest[0].bitrate)
|
|
|
|
|
|
|
|
// Old logic. Revert if needed
|
|
|
|
/* this.player.qualityLevels().levels_.sort((a, b) => {
|
2021-04-30 21:18:45 +00:00
|
|
|
if (a.height === b.height) {
|
|
|
|
return a.bitrate - b.bitrate
|
|
|
|
} else {
|
|
|
|
return a.height - b.height
|
|
|
|
}
|
2020-09-26 21:03:42 +00:00
|
|
|
}).forEach((ql, index, arr) => {
|
|
|
|
const height = ql.height
|
|
|
|
const width = ql.width
|
|
|
|
const quality = width < height ? width : height
|
|
|
|
let upperLevel = null
|
|
|
|
|
|
|
|
if (index < arr.length - 1) {
|
|
|
|
upperLevel = arr[index + 1]
|
|
|
|
}
|
|
|
|
|
2021-04-30 21:18:45 +00:00
|
|
|
if (this.defaultQuality === quality && upperLevel === null) {
|
|
|
|
this.setDashQualityLevel(height, true)
|
2020-09-26 21:03:42 +00:00
|
|
|
} else if (upperLevel !== null) {
|
|
|
|
const upperHeight = upperLevel.height
|
|
|
|
const upperWidth = upperLevel.width
|
|
|
|
const upperQuality = upperWidth < upperHeight ? upperWidth : upperHeight
|
|
|
|
|
2021-04-30 21:18:45 +00:00
|
|
|
if (this.defaultQuality >= quality && this.defaultQuality === upperQuality) {
|
|
|
|
this.setDashQualityLevel(height, true)
|
|
|
|
} else if (this.defaultQuality >= quality && this.defaultQuality < upperQuality) {
|
2021-03-12 21:09:08 +00:00
|
|
|
this.setDashQualityLevel(height)
|
2020-09-26 21:03:42 +00:00
|
|
|
}
|
|
|
|
} else if (index === 0 && quality > this.defaultQuality) {
|
2021-03-12 21:09:08 +00:00
|
|
|
this.setDashQualityLevel(height)
|
2020-09-26 21:03:42 +00:00
|
|
|
} else if (index === (arr.length - 1) && quality < this.defaultQuality) {
|
2021-03-12 21:09:08 +00:00
|
|
|
this.setDashQualityLevel(height)
|
|
|
|
}
|
2021-05-15 19:08:41 +00:00
|
|
|
}) */
|
2021-03-12 21:09:08 +00:00
|
|
|
},
|
|
|
|
|
2021-05-15 19:08:41 +00:00
|
|
|
setDashQualityLevel: function (bitrate) {
|
|
|
|
let adaptiveFormat = null
|
|
|
|
|
|
|
|
if (bitrate !== 'auto') {
|
|
|
|
adaptiveFormat = this.activeAdaptiveFormats.find((format) => {
|
|
|
|
return format.bitrate === bitrate
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-05-17 01:40:34 +00:00
|
|
|
let qualityLabel = adaptiveFormat ? adaptiveFormat.qualityLabel : ''
|
|
|
|
|
2021-05-15 19:08:41 +00:00
|
|
|
this.player.qualityLevels().levels_.sort((a, b) => {
|
|
|
|
if (a.height === b.height) {
|
|
|
|
return a.bitrate - b.bitrate
|
|
|
|
} else {
|
|
|
|
return a.height - b.height
|
|
|
|
}
|
|
|
|
}).forEach((ql, index, arr) => {
|
|
|
|
if (bitrate === 'auto' || bitrate === ql.bitrate) {
|
|
|
|
ql.enabled = true
|
|
|
|
ql.enabled_(true)
|
2021-05-17 01:40:34 +00:00
|
|
|
if (bitrate !== 'auto' && qualityLabel === '') {
|
|
|
|
qualityLabel = ql.height + 'p'
|
|
|
|
}
|
2021-05-15 19:08:41 +00:00
|
|
|
} else {
|
|
|
|
ql.enabled = false
|
|
|
|
ql.enabled_(false)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2021-05-17 01:40:34 +00:00
|
|
|
const selectedQuality = bitrate === 'auto' ? 'auto' : qualityLabel
|
2021-05-15 19:08:41 +00:00
|
|
|
|
|
|
|
const qualityElement = document.getElementById('vjs-current-quality')
|
|
|
|
qualityElement.innerText = selectedQuality
|
|
|
|
this.selectedQuality = selectedQuality
|
|
|
|
|
2022-02-19 22:17:58 +00:00
|
|
|
if (selectedQuality !== 'auto') {
|
|
|
|
this.selectedResolution = `${adaptiveFormat.width}x${adaptiveFormat.height}`
|
|
|
|
this.selectedFPS = adaptiveFormat.fps
|
|
|
|
this.selectedBitrate = adaptiveFormat.bitrate
|
|
|
|
this.selectedMimeType = adaptiveFormat.mimeType
|
|
|
|
} else {
|
|
|
|
this.selectedResolution = 'auto'
|
|
|
|
this.selectedFPS = 'auto'
|
|
|
|
this.selectedBitrate = 'auto'
|
|
|
|
this.selectedMimeType = 'auto'
|
|
|
|
}
|
|
|
|
|
2021-05-15 19:08:41 +00:00
|
|
|
const qualityItems = $('.quality-item').get()
|
|
|
|
|
|
|
|
$('.quality-item').removeClass('quality-selected')
|
|
|
|
|
|
|
|
qualityItems.forEach((item) => {
|
|
|
|
const qualityText = $(item).find('.vjs-menu-item-text').get(0)
|
|
|
|
if (qualityText.innerText === selectedQuality.toLowerCase()) {
|
|
|
|
$(item).addClass('quality-selected')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
/* if (this.selectedQuality === qualityLevel && this.using60Fps === is60Fps) {
|
2021-03-12 21:09:08 +00:00
|
|
|
return
|
|
|
|
}
|
2021-04-30 21:18:45 +00:00
|
|
|
let foundSelectedQuality = false
|
|
|
|
this.using60Fps = is60Fps
|
2021-03-12 21:09:08 +00:00
|
|
|
this.player.qualityLevels().levels_.sort((a, b) => {
|
2021-04-30 21:18:45 +00:00
|
|
|
if (a.height === b.height) {
|
|
|
|
return a.bitrate - b.bitrate
|
|
|
|
} else {
|
|
|
|
return a.height - b.height
|
|
|
|
}
|
2021-03-12 21:09:08 +00:00
|
|
|
}).forEach((ql, index, arr) => {
|
2021-04-30 21:18:45 +00:00
|
|
|
if (foundSelectedQuality) {
|
|
|
|
ql.enabled = false
|
|
|
|
ql.enabled_(false)
|
|
|
|
} else if (qualityLevel === 'auto') {
|
|
|
|
ql.enabled = true
|
|
|
|
ql.enabled_(true)
|
|
|
|
} else if (ql.height === qualityLevel) {
|
2020-09-26 21:03:42 +00:00
|
|
|
ql.enabled = true
|
2021-03-12 21:09:08 +00:00
|
|
|
ql.enabled_(true)
|
2021-04-30 21:18:45 +00:00
|
|
|
foundSelectedQuality = true
|
|
|
|
|
|
|
|
let lowerQuality
|
|
|
|
let higherQuality
|
|
|
|
|
|
|
|
if ((index - 1) !== -1) {
|
|
|
|
lowerQuality = arr[index - 1]
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((index + 1) < arr.length) {
|
|
|
|
higherQuality = arr[index + 1]
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof (lowerQuality) !== 'undefined' && lowerQuality.height === ql.height && lowerQuality.bitrate < ql.bitrate && !is60Fps) {
|
|
|
|
ql.enabled = false
|
|
|
|
ql.enabled_(false)
|
|
|
|
foundSelectedQuality = false
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof (higherQuality) !== 'undefined' && higherQuality.height === ql.height && higherQuality.bitrate > ql.bitrate && is60Fps) {
|
|
|
|
ql.enabled = false
|
|
|
|
ql.enabled_(false)
|
|
|
|
foundSelectedQuality = false
|
|
|
|
}
|
2020-09-26 21:03:42 +00:00
|
|
|
} else {
|
|
|
|
ql.enabled = false
|
2021-03-12 21:09:08 +00:00
|
|
|
ql.enabled_(false)
|
2020-09-26 21:03:42 +00:00
|
|
|
}
|
|
|
|
})
|
2021-03-12 21:09:08 +00:00
|
|
|
|
2021-04-30 21:18:45 +00:00
|
|
|
let selectedQuality = qualityLevel
|
|
|
|
|
|
|
|
if (selectedQuality !== 'auto' && is60Fps) {
|
|
|
|
selectedQuality = selectedQuality + 'p60'
|
|
|
|
} else if (selectedQuality !== 'auto') {
|
|
|
|
selectedQuality = selectedQuality + 'p'
|
|
|
|
}
|
2021-03-12 21:09:08 +00:00
|
|
|
|
|
|
|
const qualityElement = document.getElementById('vjs-current-quality')
|
|
|
|
qualityElement.innerText = selectedQuality
|
|
|
|
this.selectedQuality = qualityLevel
|
|
|
|
|
|
|
|
const qualityItems = $('.quality-item').get()
|
|
|
|
|
|
|
|
$('.quality-item').removeClass('quality-selected')
|
|
|
|
|
|
|
|
qualityItems.forEach((item) => {
|
|
|
|
const qualityText = $(item).find('.vjs-menu-item-text').get(0)
|
|
|
|
if (qualityText.innerText === selectedQuality) {
|
|
|
|
$(item).addClass('quality-selected')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
// const currentTime = this.player.currentTime()
|
|
|
|
|
|
|
|
// this.player.currentTime(0)
|
2021-05-15 19:08:41 +00:00
|
|
|
// this.player.currentTime(currentTime) */
|
2020-09-26 21:03:42 +00:00
|
|
|
},
|
|
|
|
|
2020-02-18 20:59:01 +00:00
|
|
|
enableDashFormat: function () {
|
|
|
|
if (this.dashSrc === null) {
|
|
|
|
console.log('No dash format available.')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
this.useDash = true
|
|
|
|
this.useHls = false
|
|
|
|
this.activeSourceList = this.dashSrc
|
|
|
|
|
2020-04-22 02:59:09 +00:00
|
|
|
setTimeout(this.initializePlayer, 100)
|
2020-02-18 20:59:01 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
enableLegacyFormat: function () {
|
|
|
|
if (this.sourceList.length === 0) {
|
|
|
|
console.log('No sources available')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
this.useDash = false
|
|
|
|
this.useHls = false
|
|
|
|
this.activeSourceList = this.sourceList
|
|
|
|
|
|
|
|
setTimeout(this.initializePlayer, 100)
|
2020-02-20 20:58:21 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
togglePlayPause: function () {
|
|
|
|
if (this.player.paused()) {
|
|
|
|
this.player.play()
|
|
|
|
} else {
|
|
|
|
this.player.pause()
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
changeDurationBySeconds: function (seconds) {
|
|
|
|
const currentTime = this.player.currentTime()
|
|
|
|
const newTime = currentTime + seconds
|
|
|
|
|
|
|
|
if (newTime < 0) {
|
|
|
|
this.player.currentTime(0)
|
|
|
|
} else if (newTime > this.player.duration) {
|
|
|
|
this.player.currentTime(this.player.duration)
|
|
|
|
} else {
|
|
|
|
this.player.currentTime(newTime)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
changeDurationByPercentage: function (percentage) {
|
|
|
|
const duration = this.player.duration()
|
|
|
|
const newTime = duration * percentage
|
|
|
|
|
|
|
|
this.player.currentTime(newTime)
|
|
|
|
},
|
|
|
|
|
|
|
|
changePlayBackRate: function (rate) {
|
2021-11-24 21:52:56 +00:00
|
|
|
const newPlaybackRate = (this.player.playbackRate() + rate).toFixed(2)
|
2020-02-20 20:58:21 +00:00
|
|
|
|
2021-11-24 21:52:56 +00:00
|
|
|
if (newPlaybackRate >= 0.25 && newPlaybackRate <= 8) {
|
2020-02-20 20:58:21 +00:00
|
|
|
this.player.playbackRate(newPlaybackRate)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2020-11-13 22:29:41 +00:00
|
|
|
framebyframe: function (step) {
|
|
|
|
this.player.pause()
|
2021-10-07 11:44:39 +00:00
|
|
|
const quality = this.useDash ? this.player.qualityLevels()[this.player.qualityLevels().selectedIndex] : {}
|
|
|
|
let fps = 30
|
2020-11-13 22:29:41 +00:00
|
|
|
// Non-Dash formats are 30fps only
|
2021-10-07 11:44:39 +00:00
|
|
|
if (this.maxFramerate === 60 && quality.height >= 480) {
|
|
|
|
for (let i = 0; i < this.adaptiveFormats.length; i++) {
|
|
|
|
if (this.adaptiveFormats[i].bitrate === quality.bitrate) {
|
2022-02-25 22:51:02 +00:00
|
|
|
fps = this.adaptiveFormats[i].fps ? this.adaptiveFormats[i].fps : 30
|
2021-10-07 11:44:39 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2020-11-13 22:29:41 +00:00
|
|
|
}
|
2021-10-07 11:44:39 +00:00
|
|
|
|
2020-11-13 22:29:41 +00:00
|
|
|
// The 3 lines below were taken from the videojs-framebyframe node module by Helena Rasche
|
|
|
|
const frameTime = 1 / fps
|
|
|
|
const dist = frameTime * step
|
|
|
|
this.player.currentTime(this.player.currentTime() + dist)
|
2021-10-07 11:44:39 +00:00
|
|
|
console.log(fps)
|
2020-11-13 22:29:41 +00:00
|
|
|
},
|
|
|
|
|
2020-02-20 20:58:21 +00:00
|
|
|
changeVolume: function (volume) {
|
|
|
|
const currentVolume = this.player.volume()
|
|
|
|
const newVolume = currentVolume + volume
|
|
|
|
|
|
|
|
if (newVolume < 0) {
|
|
|
|
this.player.volume(0)
|
|
|
|
} else if (newVolume > 1) {
|
|
|
|
this.player.volume(1)
|
|
|
|
} else {
|
|
|
|
this.player.volume(newVolume)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
toggleMute: function () {
|
|
|
|
if (this.player.muted()) {
|
|
|
|
this.player.muted(false)
|
|
|
|
} else {
|
|
|
|
this.player.muted(true)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
toggleCaptions: function () {
|
|
|
|
const tracks = this.player.textTracks().tracks_
|
|
|
|
|
|
|
|
if (tracks.length > 1) {
|
|
|
|
if (tracks[1].mode === 'showing') {
|
|
|
|
tracks[1].mode = 'disabled'
|
|
|
|
} else {
|
|
|
|
tracks[1].mode = 'showing'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2021-01-13 03:56:31 +00:00
|
|
|
createLoopButton: function () {
|
|
|
|
const VjsButton = videojs.getComponent('Button')
|
|
|
|
const loopButton = videojs.extend(VjsButton, {
|
|
|
|
constructor: function(player, options) {
|
|
|
|
VjsButton.call(this, player, options)
|
|
|
|
},
|
2021-05-21 23:52:11 +00:00
|
|
|
handleClick: () => {
|
|
|
|
this.toggleVideoLoop()
|
2021-01-13 03:56:31 +00:00
|
|
|
},
|
|
|
|
createControlTextEl: function (button) {
|
|
|
|
return $(button).html($('<div id="loopButton" class="vjs-icon-loop loop-white vjs-button loopWhite"></div>')
|
|
|
|
.attr('title', 'Toggle Loop'))
|
|
|
|
}
|
|
|
|
})
|
|
|
|
videojs.registerComponent('loopButton', loopButton)
|
|
|
|
},
|
|
|
|
|
|
|
|
toggleVideoLoop: async function () {
|
|
|
|
if (!this.player.loop()) {
|
|
|
|
const currentTheme = localStorage.getItem('mainColor')
|
|
|
|
const colorNames = this.$store.state.utils.colorClasses
|
|
|
|
const colorValues = this.$store.state.utils.colorValues
|
|
|
|
|
|
|
|
const nameIndex = colorNames.findIndex((color) => {
|
|
|
|
return color === currentTheme
|
|
|
|
})
|
|
|
|
|
|
|
|
const themeTextColor = await this.calculateColorLuminance(colorValues[nameIndex])
|
|
|
|
|
|
|
|
$('#loopButton').addClass('vjs-icon-loop-active')
|
|
|
|
|
|
|
|
if (themeTextColor === '#000000') {
|
|
|
|
$('#loopButton').addClass('loop-black')
|
|
|
|
$('#loopButton').removeClass('loop-white')
|
|
|
|
}
|
|
|
|
|
|
|
|
this.player.loop(true)
|
|
|
|
} else {
|
|
|
|
$('#loopButton').removeClass('vjs-icon-loop-active')
|
|
|
|
$('#loopButton').removeClass('loop-black')
|
|
|
|
$('#loopButton').addClass('loop-white')
|
|
|
|
this.player.loop(false)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
createFullWindowButton: function () {
|
2020-12-14 22:37:58 +00:00
|
|
|
const VjsButton = videojs.getComponent('Button')
|
|
|
|
const fullWindowButton = videojs.extend(VjsButton, {
|
|
|
|
constructor: function(player, options) {
|
|
|
|
VjsButton.call(this, player, options)
|
|
|
|
},
|
2021-05-21 23:52:11 +00:00
|
|
|
handleClick: () => {
|
|
|
|
this.toggleFullWindow()
|
2020-12-14 22:37:58 +00:00
|
|
|
},
|
|
|
|
createControlTextEl: function (button) {
|
2021-04-22 18:41:50 +00:00
|
|
|
// Add class name to button to be able to target it with CSS selector
|
|
|
|
return $(button)
|
|
|
|
.addClass('vjs-button-fullwindow')
|
|
|
|
.html($('<div id="fullwindow" class="vjs-icon-fullwindow-enter vjs-button"></div>')
|
|
|
|
.attr('title', 'Full Window'))
|
2020-12-14 22:37:58 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
videojs.registerComponent('fullWindowButton', fullWindowButton)
|
|
|
|
},
|
|
|
|
|
2021-08-24 07:36:10 +00:00
|
|
|
createToggleTheatreModeButton: function() {
|
|
|
|
if (!this.$parent.theatrePossible) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-09-02 18:22:06 +00:00
|
|
|
const theatreModeActive = this.$parent.useTheatreMode ? ' vjs-icon-theatre-active' : ''
|
2021-08-24 07:36:10 +00:00
|
|
|
|
|
|
|
const VjsButton = videojs.getComponent('Button')
|
|
|
|
const toggleTheatreModeButton = videojs.extend(VjsButton, {
|
|
|
|
constructor: function(player, options) {
|
|
|
|
VjsButton.call(this, player, options)
|
|
|
|
},
|
|
|
|
handleClick: () => {
|
|
|
|
this.toggleTheatreMode()
|
|
|
|
},
|
|
|
|
createControlTextEl: function (button) {
|
|
|
|
return $(button)
|
|
|
|
.addClass('vjs-button-theatre')
|
|
|
|
.html($(`<div id="toggleTheatreModeButton" class="vjs-icon-theatre-inactive${theatreModeActive} vjs-button"></div>`))
|
|
|
|
.attr('title', 'Toggle Theatre Mode')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
videojs.registerComponent('toggleTheatreModeButton', toggleTheatreModeButton)
|
|
|
|
},
|
|
|
|
|
|
|
|
toggleTheatreMode: function() {
|
|
|
|
if (!this.player.isFullscreen_) {
|
|
|
|
const toggleTheatreModeButton = $('#toggleTheatreModeButton')
|
|
|
|
if (!this.$parent.useTheatreMode) {
|
|
|
|
toggleTheatreModeButton.addClass('vjs-icon-theatre-active')
|
|
|
|
} else {
|
|
|
|
toggleTheatreModeButton.removeClass('vjs-icon-theatre-active')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.$parent.toggleTheatreMode()
|
|
|
|
},
|
|
|
|
|
2021-03-12 21:09:08 +00:00
|
|
|
createDashQualitySelector: function (levels) {
|
|
|
|
if (levels.levels_.length === 0) {
|
|
|
|
setTimeout(() => {
|
|
|
|
this.createDashQualitySelector(this.player.qualityLevels())
|
|
|
|
}, 200)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const VjsButton = videojs.getComponent('Button')
|
|
|
|
const dashQualitySelector = videojs.extend(VjsButton, {
|
|
|
|
constructor: function(player, options) {
|
|
|
|
VjsButton.call(this, player, options)
|
|
|
|
},
|
2021-05-21 23:52:11 +00:00
|
|
|
handleClick: (event) => {
|
2021-05-15 19:08:41 +00:00
|
|
|
console.log(event)
|
2021-03-12 21:09:08 +00:00
|
|
|
const selectedQuality = event.target.innerText
|
2021-05-15 19:08:41 +00:00
|
|
|
const bitrate = selectedQuality === 'auto' ? 'auto' : parseInt(event.target.attributes.bitrate.value)
|
2021-05-21 23:52:11 +00:00
|
|
|
this.setDashQualityLevel(bitrate)
|
2021-03-12 21:09:08 +00:00
|
|
|
},
|
2021-05-21 23:52:11 +00:00
|
|
|
createControlTextEl: (button) => {
|
2021-03-12 21:09:08 +00:00
|
|
|
const beginningHtml = `<div class="vjs-quality-level-value">
|
|
|
|
<span id="vjs-current-quality">1080p</span>
|
|
|
|
</div>
|
|
|
|
<div class="vjs-quality-level-menu vjs-menu">
|
|
|
|
<ul class="vjs-menu-content" role="menu">`
|
|
|
|
const endingHtml = '</ul></div>'
|
|
|
|
|
2021-05-15 19:08:41 +00:00
|
|
|
let qualityHtml = `<li class="vjs-menu-item quality-item" role="menuitemradio" tabindex="-1" aria-checked="false" aria-disabled="false">
|
2021-03-12 21:09:08 +00:00
|
|
|
<span class="vjs-menu-item-text">Auto</span>
|
|
|
|
<span class="vjs-control-text" aria-live="polite"></span>
|
|
|
|
</li>`
|
|
|
|
|
|
|
|
levels.levels_.sort((a, b) => {
|
2021-04-30 21:18:45 +00:00
|
|
|
if (b.height === a.height) {
|
|
|
|
return b.bitrate - a.bitrate
|
|
|
|
} else {
|
|
|
|
return b.height - a.height
|
|
|
|
}
|
|
|
|
}).forEach((quality, index, array) => {
|
2021-05-17 01:40:34 +00:00
|
|
|
let fps
|
|
|
|
let qualityLabel
|
|
|
|
let bitrate
|
2021-05-15 19:08:41 +00:00
|
|
|
|
2021-05-21 23:52:11 +00:00
|
|
|
if (typeof this.adaptiveFormats !== 'undefined' && this.adaptiveFormats.length > 0) {
|
|
|
|
const adaptiveFormat = this.adaptiveFormats.find((format) => {
|
2021-05-17 01:40:34 +00:00
|
|
|
return format.bitrate === quality.bitrate
|
|
|
|
})
|
2021-05-15 19:08:41 +00:00
|
|
|
|
2022-02-25 22:51:02 +00:00
|
|
|
if (typeof adaptiveFormat === 'undefined') {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-05-21 23:52:11 +00:00
|
|
|
this.activeAdaptiveFormats.push(adaptiveFormat)
|
2021-05-17 01:40:34 +00:00
|
|
|
|
2021-10-12 21:03:22 +00:00
|
|
|
fps = adaptiveFormat.fps ? adaptiveFormat.fps : 30
|
2021-05-17 01:40:34 +00:00
|
|
|
qualityLabel = adaptiveFormat.qualityLabel ? adaptiveFormat.qualityLabel : quality.height + 'p'
|
|
|
|
bitrate = quality.bitrate
|
|
|
|
} else {
|
|
|
|
fps = 30
|
|
|
|
qualityLabel = quality.height + 'p'
|
|
|
|
bitrate = quality.bitrate
|
|
|
|
}
|
2021-05-15 19:08:41 +00:00
|
|
|
|
|
|
|
qualityHtml = qualityHtml + `<li class="vjs-menu-item quality-item" role="menuitemradio" tabindex="-1" aria-checked="false" aria-disabled="false" fps="${fps}" bitrate="${bitrate}">
|
|
|
|
<span class="vjs-menu-item-text" fps="${fps}" bitrate="${bitrate}">${qualityLabel}</span>
|
|
|
|
<span class="vjs-control-text" aria-live="polite"></span>
|
|
|
|
</li>`
|
|
|
|
|
|
|
|
// Old logic, revert if needed.
|
|
|
|
/* let is60Fps = false
|
2021-04-30 21:18:45 +00:00
|
|
|
if (index < array.length - 1 && array[index + 1].height === quality.height) {
|
|
|
|
if (array[index + 1].bitrate < quality.bitrate) {
|
|
|
|
is60Fps = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const qualityText = is60Fps ? quality.height + 'p60' : quality.height + 'p'
|
2021-03-12 21:09:08 +00:00
|
|
|
qualityHtml = qualityHtml + `<li class="vjs-menu-item quality-item" role="menuitemradio" tabindex="-1" aria-checked="false aria-disabled="false">
|
2021-04-30 21:18:45 +00:00
|
|
|
<span class="vjs-menu-item-text">${qualityText}</span>
|
2021-03-12 21:09:08 +00:00
|
|
|
<span class="vjs-control-text" aria-live="polite"></span>
|
2021-05-15 19:08:41 +00:00
|
|
|
</li>` */
|
2021-03-12 21:09:08 +00:00
|
|
|
})
|
|
|
|
return $(button).html(
|
|
|
|
$(beginningHtml + qualityHtml + endingHtml).attr(
|
|
|
|
'title',
|
|
|
|
'Select Quality'
|
|
|
|
))
|
|
|
|
}
|
|
|
|
})
|
|
|
|
videojs.registerComponent('dashQualitySelector', dashQualitySelector)
|
|
|
|
this.player.controlBar.addChild('dashQualitySelector', {}, this.player.controlBar.children_.length - 1)
|
|
|
|
this.determineDefaultQualityDash()
|
|
|
|
},
|
|
|
|
|
2021-09-20 02:12:14 +00:00
|
|
|
sortCaptions: function (captionList) {
|
|
|
|
return captionList.sort((captionA, captionB) => {
|
|
|
|
const aCode = captionA.languageCode.split('-') // ex. [en,US]
|
|
|
|
const bCode = captionB.languageCode.split('-')
|
|
|
|
const aName = (captionA.label || captionA.name.simpleText) // ex: english (auto-generated)
|
|
|
|
const bName = (captionB.label || captionB.name.simpleText)
|
|
|
|
const userLocale = this.currentLocale.split(/-|_/) // ex. [en,US]
|
|
|
|
if (aCode[0] === userLocale[0]) { // caption a has same language as user's locale
|
|
|
|
if (bCode[0] === userLocale[0]) { // caption b has same language as user's locale
|
|
|
|
if (bName.search('auto') !== -1) {
|
|
|
|
// prefer caption a: b is auto-generated captions
|
|
|
|
return -1
|
|
|
|
} else if (aName.search('auto') !== -1) {
|
|
|
|
// prefer caption b: a is auto-generated captions
|
|
|
|
return 1
|
|
|
|
} else if (aCode[1] === userLocale[1]) {
|
|
|
|
// prefer caption a: caption a has same county code as user's locale
|
|
|
|
return -1
|
|
|
|
} else if (bCode[1] === userLocale[1]) {
|
|
|
|
// prefer caption b: caption b has same county code as user's locale
|
|
|
|
return 1
|
|
|
|
} else if (aCode[1] === undefined) {
|
|
|
|
// prefer caption a: no country code is better than wrong country code
|
|
|
|
return -1
|
|
|
|
} else if (bCode[1] === undefined) {
|
|
|
|
// prefer caption b: no country code is better than wrong country code
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// prefer caption a: b does not match user's language
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
} else if (bCode[0] === userLocale[0]) {
|
|
|
|
// prefer caption b: a does not match user's language
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
// sort alphabetically
|
|
|
|
return aName.localeCompare(bName)
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2021-03-17 01:28:25 +00:00
|
|
|
transformAndInsertCaptions: async function() {
|
|
|
|
let captionList
|
|
|
|
if (this.captionHybridList[0] instanceof Promise) {
|
|
|
|
captionList = await Promise.all(this.captionHybridList)
|
|
|
|
this.$emit('store-caption-list', captionList)
|
|
|
|
} else {
|
|
|
|
captionList = this.captionHybridList
|
|
|
|
}
|
|
|
|
|
2021-09-20 02:12:14 +00:00
|
|
|
for (const caption of this.sortCaptions(captionList)) {
|
2021-03-17 01:28:25 +00:00
|
|
|
this.player.addRemoteTextTrack({
|
|
|
|
kind: 'subtitles',
|
|
|
|
src: caption.baseUrl || caption.url,
|
|
|
|
srclang: caption.languageCode,
|
|
|
|
label: caption.label || caption.name.simpleText,
|
|
|
|
type: caption.type
|
|
|
|
}, true)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2020-12-14 22:37:58 +00:00
|
|
|
toggleFullWindow: function() {
|
|
|
|
if (!this.player.isFullscreen_) {
|
|
|
|
if (this.player.isFullWindow) {
|
|
|
|
this.player.removeClass('vjs-full-screen')
|
|
|
|
this.player.isFullWindow = false
|
|
|
|
document.documentElement.style.overflow = this.player.docOrigOverflow
|
|
|
|
$('body').removeClass('vjs-full-window')
|
|
|
|
$('#fullwindow').removeClass('vjs-icon-fullwindow-exit')
|
|
|
|
this.player.trigger('exitFullWindow')
|
|
|
|
} else {
|
|
|
|
this.player.addClass('vjs-full-screen')
|
|
|
|
this.player.isFullscreen_ = false
|
|
|
|
this.player.isFullWindow = true
|
|
|
|
this.player.docOrigOverflow = document.documentElement.style.overflow
|
|
|
|
document.documentElement.style.overflow = 'hidden'
|
|
|
|
$('body').addClass('vjs-full-window')
|
|
|
|
$('#fullwindow').addClass('vjs-icon-fullwindow-exit')
|
|
|
|
this.player.trigger('enterFullWindow')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
exitFullWindow: function() {
|
|
|
|
if (this.player.isFullWindow) {
|
|
|
|
this.player.isFullWindow = false
|
|
|
|
document.documentElement.style.overflow = this.player.docOrigOverflow
|
|
|
|
this.player.removeClass('vjs-full-screen')
|
|
|
|
$('body').removeClass('vjs-full-window')
|
|
|
|
$('#fullwindow').removeClass('vjs-icon-fullwindow-exit')
|
|
|
|
this.player.trigger('exitFullWindow')
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
toggleFullscreen: function () {
|
|
|
|
if (this.player.isFullscreen()) {
|
|
|
|
this.player.exitFullscreen()
|
|
|
|
} else {
|
|
|
|
this.player.requestFullscreen()
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2020-02-21 18:31:32 +00:00
|
|
|
hideMouseTimeout: function () {
|
|
|
|
if (this.id === '') {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const videoPlayer = $(`#${this.id} video`).get(0)
|
|
|
|
if (typeof (videoPlayer) !== 'undefined') {
|
|
|
|
videoPlayer.style.cursor = 'default'
|
|
|
|
clearTimeout(this.mouseTimeout)
|
|
|
|
this.mouseTimeout = window.setTimeout(function () {
|
|
|
|
videoPlayer.style.cursor = 'none'
|
|
|
|
}, 2650)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
removeMouseTimeout: function () {
|
|
|
|
if (this.mouseTimeout !== null) {
|
|
|
|
clearTimeout(this.mouseTimeout)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2020-11-15 19:13:18 +00:00
|
|
|
fullscreenOverlay: function () {
|
|
|
|
const title = document.title.replace('- FreeTube', '')
|
|
|
|
|
|
|
|
if (this.player.isFullscreen()) {
|
2021-05-21 23:52:11 +00:00
|
|
|
this.player.ready(() => {
|
|
|
|
this.player.overlay({
|
2020-11-15 19:13:18 +00:00
|
|
|
overlays: [{
|
|
|
|
showBackground: false,
|
|
|
|
content: title,
|
|
|
|
start: 'mousemove',
|
|
|
|
end: 'userinactive'
|
|
|
|
}]
|
|
|
|
})
|
|
|
|
})
|
|
|
|
} else {
|
2021-05-21 23:52:11 +00:00
|
|
|
this.player.ready(() => {
|
|
|
|
this.player.overlay({
|
2020-11-15 19:13:18 +00:00
|
|
|
overlays: [{
|
|
|
|
showBackground: false,
|
|
|
|
content: ' ',
|
|
|
|
start: 'play',
|
|
|
|
end: 'loadstart'
|
|
|
|
}]
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2021-04-22 18:41:50 +00:00
|
|
|
toggleFullscreenClass: function () {
|
|
|
|
if (this.player.isFullscreen()) {
|
|
|
|
$('body').addClass('vjs--full-screen-enabled')
|
|
|
|
} else {
|
|
|
|
$('body').removeClass('vjs--full-screen-enabled')
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2021-01-16 03:34:49 +00:00
|
|
|
handleTouchStart: function (event) {
|
|
|
|
this.touchPauseTimeout = setTimeout(() => {
|
2021-05-21 23:52:11 +00:00
|
|
|
this.togglePlayPause()
|
2021-01-16 03:34:49 +00:00
|
|
|
}, 1000)
|
|
|
|
|
|
|
|
const touchTime = new Date()
|
|
|
|
|
|
|
|
if (this.lastTouchTime !== null && (touchTime.getTime() - this.lastTouchTime.getTime()) < 250) {
|
|
|
|
this.toggleFullscreen()
|
|
|
|
}
|
|
|
|
|
|
|
|
this.lastTouchTime = touchTime
|
|
|
|
},
|
|
|
|
|
|
|
|
handleTouchEnd: function (event) {
|
|
|
|
clearTimeout(this.touchPauseTimeout)
|
|
|
|
},
|
2022-02-19 22:17:58 +00:00
|
|
|
toggleShowStatsModal: function() {
|
|
|
|
console.log(this.format)
|
|
|
|
if (this.format !== 'dash') {
|
|
|
|
this.showToast({
|
2022-02-20 02:32:34 +00:00
|
|
|
message: this.$t('Video.Stats.Video statistics are not available for legacy videos')
|
2022-02-19 22:17:58 +00:00
|
|
|
})
|
|
|
|
} else {
|
|
|
|
this.showStatsModal = !this.showStatsModal
|
|
|
|
}
|
|
|
|
},
|
|
|
|
createStatsModal: function() {
|
|
|
|
const ModalDialog = videojs.getComponent('ModalDialog')
|
|
|
|
this.statsModal = new ModalDialog(this.player, {
|
|
|
|
temporary: false,
|
|
|
|
pauseOnOpen: false
|
|
|
|
})
|
|
|
|
this.player.addChild(this.statsModal)
|
|
|
|
this.statsModal.el_.classList.add('statsModal')
|
|
|
|
this.statsModal.on('modalclose', () => {
|
|
|
|
this.showStatsModal = false
|
|
|
|
})
|
|
|
|
},
|
|
|
|
updateStatsContent: function() {
|
|
|
|
if (this.showStatsModal) {
|
|
|
|
this.statsModal.contentEl().innerHTML = this.getFormattedStats()
|
|
|
|
}
|
|
|
|
},
|
|
|
|
getFormattedStats: function() {
|
|
|
|
const currentVolume = this.player.muted() ? 0 : this.player.volume()
|
|
|
|
const volume = `${(currentVolume * 100).toFixed(0)}%`
|
|
|
|
const bandwidth = `${(this.playerStats.bandwidth / 1000).toFixed(2)}kbps`
|
|
|
|
const buffered = `${(this.player.bufferedPercent() * 100).toFixed(0)}%`
|
|
|
|
const droppedFrames = this.playerStats.videoPlaybackQuality.droppedVideoFrames
|
|
|
|
const totalFrames = this.playerStats.videoPlaybackQuality.totalVideoFrames
|
|
|
|
const frames = `${droppedFrames} / ${totalFrames}`
|
|
|
|
const resolution = `${this.selectedResolution}@${this.selectedFPS}fps`
|
|
|
|
const playerDimensions = `${this.playerStats.playerDimensions.width}x${this.playerStats.playerDimensions.height}`
|
|
|
|
const statsArray = [
|
|
|
|
[this.$t('Video.Stats.Video ID'), this.videoId],
|
|
|
|
[this.$t('Video.Stats.Resolution'), resolution],
|
|
|
|
[this.$t('Video.Stats.Player Dimensions'), playerDimensions],
|
|
|
|
[this.$t('Video.Stats.Bitrate'), this.selectedBitrate],
|
|
|
|
[this.$t('Video.Stats.Volume'), volume],
|
|
|
|
[this.$t('Video.Stats.Bandwidth'), bandwidth],
|
|
|
|
[this.$t('Video.Stats.Buffered'), buffered],
|
|
|
|
[this.$t('Video.Stats.Dropped / Total Frames'), frames],
|
|
|
|
[this.$t('Video.Stats.Mimetype'), this.selectedMimeType]
|
|
|
|
]
|
|
|
|
let listContentHTML = ''
|
|
|
|
|
|
|
|
statsArray.forEach((stat) => {
|
|
|
|
const content = `<p>${stat[0]}: ${stat[1]}</p>`
|
|
|
|
listContentHTML += content
|
|
|
|
})
|
|
|
|
return listContentHTML
|
|
|
|
},
|
2021-01-16 03:34:49 +00:00
|
|
|
|
2022-02-19 22:17:58 +00:00
|
|
|
// This function should always be at the bottom of this file
|
2020-02-20 20:58:21 +00:00
|
|
|
keyboardShortcutHandler: function (event) {
|
|
|
|
const activeInputs = $('.ft-input')
|
|
|
|
|
|
|
|
for (let i = 0; i < activeInputs.length; i++) {
|
|
|
|
if (activeInputs[i] === document.activeElement) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-21 19:43:50 +00:00
|
|
|
if (event.ctrlKey) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-02-20 20:58:21 +00:00
|
|
|
if (this.player !== null) {
|
|
|
|
switch (event.which) {
|
|
|
|
case 32:
|
|
|
|
// Space Bar
|
|
|
|
// Toggle Play/Pause
|
2020-09-26 15:57:57 +00:00
|
|
|
event.preventDefault()
|
2020-02-20 20:58:21 +00:00
|
|
|
this.togglePlayPause()
|
|
|
|
break
|
|
|
|
case 74:
|
|
|
|
// J Key
|
2021-08-05 20:17:01 +00:00
|
|
|
// Rewind by 2x the time-skip interval (in seconds)
|
2020-09-26 15:57:57 +00:00
|
|
|
event.preventDefault()
|
2022-03-30 02:06:54 +00:00
|
|
|
this.changeDurationBySeconds(-this.defaultSkipInterval * this.player.playbackRate() * 2)
|
2020-02-20 20:58:21 +00:00
|
|
|
break
|
|
|
|
case 75:
|
|
|
|
// K Key
|
|
|
|
// Toggle Play/Pause
|
2020-09-26 15:57:57 +00:00
|
|
|
event.preventDefault()
|
2020-02-20 20:58:21 +00:00
|
|
|
this.togglePlayPause()
|
|
|
|
break
|
|
|
|
case 76:
|
|
|
|
// L Key
|
2021-08-05 20:17:01 +00:00
|
|
|
// Fast-Forward by 2x the time-skip interval (in seconds)
|
2020-09-26 15:57:57 +00:00
|
|
|
event.preventDefault()
|
2022-03-30 02:06:54 +00:00
|
|
|
this.changeDurationBySeconds(this.defaultSkipInterval * this.player.playbackRate() * 2)
|
2020-02-20 20:58:21 +00:00
|
|
|
break
|
|
|
|
case 79:
|
|
|
|
// O Key
|
|
|
|
// Decrease playback rate by 0.25x
|
2020-09-26 15:57:57 +00:00
|
|
|
event.preventDefault()
|
2020-02-20 20:58:21 +00:00
|
|
|
this.changePlayBackRate(-0.25)
|
|
|
|
break
|
|
|
|
case 80:
|
|
|
|
// P Key
|
|
|
|
// Increase playback rate by 0.25x
|
2020-09-26 15:57:57 +00:00
|
|
|
event.preventDefault()
|
2020-02-20 20:58:21 +00:00
|
|
|
this.changePlayBackRate(0.25)
|
|
|
|
break
|
|
|
|
case 70:
|
|
|
|
// F Key
|
|
|
|
// Toggle Fullscreen Playback
|
2020-09-26 15:57:57 +00:00
|
|
|
event.preventDefault()
|
2020-02-20 20:58:21 +00:00
|
|
|
this.toggleFullscreen()
|
|
|
|
break
|
|
|
|
case 77:
|
|
|
|
// M Key
|
|
|
|
// Toggle Mute
|
2020-09-26 15:57:57 +00:00
|
|
|
event.preventDefault()
|
2020-02-20 20:58:21 +00:00
|
|
|
this.toggleMute()
|
|
|
|
break
|
|
|
|
case 67:
|
|
|
|
// C Key
|
|
|
|
// Toggle Captions
|
2020-09-26 15:57:57 +00:00
|
|
|
event.preventDefault()
|
2020-02-20 20:58:21 +00:00
|
|
|
this.toggleCaptions()
|
|
|
|
break
|
|
|
|
case 38:
|
|
|
|
// Up Arrow Key
|
|
|
|
// Increase volume
|
2020-09-26 15:57:57 +00:00
|
|
|
event.preventDefault()
|
2020-02-20 20:58:21 +00:00
|
|
|
this.changeVolume(0.05)
|
|
|
|
break
|
|
|
|
case 40:
|
|
|
|
// Down Arrow Key
|
2020-11-15 19:13:18 +00:00
|
|
|
// Decrease Volume
|
2020-09-26 15:57:57 +00:00
|
|
|
event.preventDefault()
|
2020-02-20 20:58:21 +00:00
|
|
|
this.changeVolume(-0.05)
|
|
|
|
break
|
|
|
|
case 37:
|
|
|
|
// Left Arrow Key
|
2021-08-05 20:17:01 +00:00
|
|
|
// Rewind by the time-skip interval (in seconds)
|
2020-09-26 15:57:57 +00:00
|
|
|
event.preventDefault()
|
2022-03-30 02:06:54 +00:00
|
|
|
this.changeDurationBySeconds(-this.defaultSkipInterval * this.player.playbackRate())
|
2020-02-20 20:58:21 +00:00
|
|
|
break
|
|
|
|
case 39:
|
|
|
|
// Right Arrow Key
|
2021-08-05 20:17:01 +00:00
|
|
|
// Fast-Forward by the time-skip interval (in seconds)
|
2020-09-26 15:57:57 +00:00
|
|
|
event.preventDefault()
|
2022-03-30 02:06:54 +00:00
|
|
|
this.changeDurationBySeconds(this.defaultSkipInterval * this.player.playbackRate())
|
2020-02-20 20:58:21 +00:00
|
|
|
break
|
2022-02-19 22:17:58 +00:00
|
|
|
case 73:
|
|
|
|
// I Key
|
|
|
|
event.preventDefault()
|
|
|
|
this.toggleShowStatsModal()
|
|
|
|
break
|
2020-02-20 20:58:21 +00:00
|
|
|
case 49:
|
|
|
|
// 1 Key
|
|
|
|
// Jump to 10% in the video
|
2020-09-26 15:57:57 +00:00
|
|
|
event.preventDefault()
|
2020-02-20 20:58:21 +00:00
|
|
|
this.changeDurationByPercentage(0.1)
|
|
|
|
break
|
|
|
|
case 50:
|
|
|
|
// 2 Key
|
|
|
|
// Jump to 20% in the video
|
2020-09-26 15:57:57 +00:00
|
|
|
event.preventDefault()
|
2020-02-20 20:58:21 +00:00
|
|
|
this.changeDurationByPercentage(0.2)
|
|
|
|
break
|
|
|
|
case 51:
|
|
|
|
// 3 Key
|
|
|
|
// Jump to 30% in the video
|
2020-09-26 15:57:57 +00:00
|
|
|
event.preventDefault()
|
2020-02-20 20:58:21 +00:00
|
|
|
this.changeDurationByPercentage(0.3)
|
|
|
|
break
|
|
|
|
case 52:
|
|
|
|
// 4 Key
|
|
|
|
// Jump to 40% in the video
|
2020-09-26 15:57:57 +00:00
|
|
|
event.preventDefault()
|
2020-02-20 20:58:21 +00:00
|
|
|
this.changeDurationByPercentage(0.4)
|
|
|
|
break
|
|
|
|
case 53:
|
|
|
|
// 5 Key
|
|
|
|
// Jump to 50% in the video
|
2020-09-26 15:57:57 +00:00
|
|
|
event.preventDefault()
|
2020-02-20 20:58:21 +00:00
|
|
|
this.changeDurationByPercentage(0.5)
|
|
|
|
break
|
|
|
|
case 54:
|
|
|
|
// 6 Key
|
|
|
|
// Jump to 60% in the video
|
2020-09-26 15:57:57 +00:00
|
|
|
event.preventDefault()
|
2020-02-20 20:58:21 +00:00
|
|
|
this.changeDurationByPercentage(0.6)
|
|
|
|
break
|
|
|
|
case 55:
|
|
|
|
// 7 Key
|
|
|
|
// Jump to 70% in the video
|
2020-09-26 15:57:57 +00:00
|
|
|
event.preventDefault()
|
2020-02-20 20:58:21 +00:00
|
|
|
this.changeDurationByPercentage(0.7)
|
|
|
|
break
|
|
|
|
case 56:
|
|
|
|
// 8 Key
|
|
|
|
// Jump to 80% in the video
|
2020-09-26 15:57:57 +00:00
|
|
|
event.preventDefault()
|
2020-02-20 20:58:21 +00:00
|
|
|
this.changeDurationByPercentage(0.8)
|
|
|
|
break
|
|
|
|
case 57:
|
|
|
|
// 9 Key
|
|
|
|
// Jump to 90% in the video
|
2020-09-26 15:57:57 +00:00
|
|
|
event.preventDefault()
|
2020-02-20 20:58:21 +00:00
|
|
|
this.changeDurationByPercentage(0.9)
|
|
|
|
break
|
|
|
|
case 48:
|
|
|
|
// 0 Key
|
|
|
|
// Jump to 0% in the video (The beginning)
|
2020-09-26 15:57:57 +00:00
|
|
|
event.preventDefault()
|
2020-02-20 20:58:21 +00:00
|
|
|
this.changeDurationByPercentage(0)
|
|
|
|
break
|
2020-11-13 22:29:41 +00:00
|
|
|
case 188:
|
|
|
|
// , Key
|
|
|
|
// Return to previous frame
|
|
|
|
this.framebyframe(-1)
|
|
|
|
break
|
|
|
|
case 190:
|
|
|
|
// . Key
|
|
|
|
// Advance to next frame
|
|
|
|
this.framebyframe(1)
|
|
|
|
break
|
2020-12-15 19:07:40 +00:00
|
|
|
case 68:
|
|
|
|
// D Key
|
|
|
|
// Toggle Picture in Picture Mode
|
|
|
|
if (!this.player.isInPictureInPicture()) {
|
|
|
|
this.player.requestPictureInPicture()
|
|
|
|
} else if (this.player.isInPictureInPicture()) {
|
|
|
|
this.player.exitPictureInPicture()
|
|
|
|
}
|
|
|
|
break
|
2020-12-14 22:37:58 +00:00
|
|
|
case 27:
|
|
|
|
// esc Key
|
|
|
|
// Exit full window
|
|
|
|
event.preventDefault()
|
|
|
|
this.exitFullWindow()
|
|
|
|
break
|
2020-12-15 19:07:40 +00:00
|
|
|
case 83:
|
|
|
|
// S Key
|
|
|
|
// Toggle Full Window Mode
|
|
|
|
this.toggleFullWindow()
|
|
|
|
break
|
2021-09-15 02:00:34 +00:00
|
|
|
case 84:
|
|
|
|
// T Key
|
|
|
|
// Toggle Theatre Mode
|
|
|
|
this.toggleTheatreMode()
|
|
|
|
break
|
2020-02-20 20:58:21 +00:00
|
|
|
}
|
|
|
|
}
|
2021-01-13 03:56:31 +00:00
|
|
|
},
|
|
|
|
|
2022-01-11 02:16:50 +00:00
|
|
|
...mapActions([
|
|
|
|
'calculateColorLuminance',
|
|
|
|
'updateDefaultCaptionSettings',
|
|
|
|
'showToast',
|
|
|
|
'sponsorBlockSkipSegments'
|
|
|
|
])
|
2020-02-16 18:30:00 +00:00
|
|
|
}
|
|
|
|
})
|