From e367da949bb97c3db8c2f9a28ea09eef93abb2f5 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 6 Sep 2021 16:08:59 +0200 Subject: Cleanup player quality change --- client/package.json | 1 - .../assets/player/p2p-media-loader/hls-plugin.ts | 311 +++------------------ .../p2p-media-loader/p2p-media-loader-plugin.ts | 8 - .../src/assets/player/peertube-player-manager.ts | 2 +- client/src/assets/player/peertube-plugin.ts | 38 +-- .../assets/player/peertube-resolutions-plugin.ts | 88 ++++++ .../src/assets/player/peertube-videojs-typings.ts | 38 +-- .../videojs-components/resolution-menu-button.ts | 41 +-- .../videojs-components/resolution-menu-item.ts | 48 ++-- .../videojs-components/settings-menu-item.ts | 2 +- .../assets/player/webtorrent/webtorrent-plugin.ts | 91 +++--- client/src/standalone/videos/embed-api.ts | 37 +-- client/yarn.lock | 8 - 13 files changed, 223 insertions(+), 490 deletions(-) create mode 100644 client/src/assets/player/peertube-resolutions-plugin.ts (limited to 'client') diff --git a/client/package.json b/client/package.json index fed8f58b2..7d3edd0a7 100644 --- a/client/package.json +++ b/client/package.json @@ -128,7 +128,6 @@ "typescript": "~4.3.4", "video.js": "^7", "videojs-contextmenu-pt": "^5.4.1", - "videojs-contrib-quality-levels": "^2.0.9", "videojs-dock": "^2.0.2", "videojs-hotkeys": "^0.2.27", "videostream": "~3.2.1", diff --git a/client/src/assets/player/p2p-media-loader/hls-plugin.ts b/client/src/assets/player/p2p-media-loader/hls-plugin.ts index a1b07aea6..17b9aba97 100644 --- a/client/src/assets/player/p2p-media-loader/hls-plugin.ts +++ b/client/src/assets/player/p2p-media-loader/hls-plugin.ts @@ -1,9 +1,9 @@ // Thanks https://github.com/streamroot/videojs-hlsjs-plugin // We duplicated this plugin to choose the hls.js version we want, because streamroot only provide a bundled file -import Hlsjs, { ErrorData, HlsConfig, Level, ManifestLoadedData } from 'hls.js' +import Hlsjs, { ErrorData, HlsConfig, Level, LevelSwitchingData, ManifestParsedData } from 'hls.js' import videojs from 'video.js' -import { HlsjsConfigHandlerOptions, QualityLevelRepresentation, QualityLevels, VideoJSTechHLS } from '../peertube-videojs-typings' +import { HlsjsConfigHandlerOptions, PeerTubeResolution, VideoJSTechHLS } from '../peertube-videojs-typings' type ErrorCounts = { [ type: string ]: number @@ -102,15 +102,10 @@ class Html5Hlsjs { private dvrDuration: number = null private edgeMargin: number = null - private handlers: { [ id in 'play' | 'playing' | 'textTracksChange' | 'audioTracksChange' ]: EventListener } = { - play: null, - playing: null, - textTracksChange: null, - audioTracksChange: null + private handlers: { [ id in 'play' ]: EventListener } = { + play: null } - private uiTextTrackHandled = false - constructor (vjs: typeof videojs, source: videojs.Tech.SourceObject, tech: videojs.Tech) { this.vjs = vjs this.source = source @@ -177,10 +172,6 @@ class Html5Hlsjs { // See comment for `initialize` method. dispose () { this.videoElement.removeEventListener('play', this.handlers.play) - this.videoElement.removeEventListener('playing', this.handlers.playing) - - this.player.textTracks().removeEventListener('change', this.handlers.textTracksChange) - this.uiTextTrackHandled = false this.hls.destroy() } @@ -281,11 +272,7 @@ class Html5Hlsjs { } } - private switchQuality (qualityId: number) { - this.hls.currentLevel = qualityId - } - - private _levelLabel (level: Level) { + private buildLevelLabel (level: Level) { if (this.player.srOptions_.levelLabelHandler) { return this.player.srOptions_.levelLabelHandler(level as any) } @@ -294,167 +281,37 @@ class Html5Hlsjs { if (level.width) return Math.round(level.width * 9 / 16) + 'p' if (level.bitrate) return (level.bitrate / 1000) + 'kbps' - return 0 - } - - private _relayQualityChange (qualityLevels: QualityLevels) { - // Determine if it is "Auto" (all tracks enabled) - let isAuto = true - - for (let i = 0; i < qualityLevels.length; i++) { - if (!qualityLevels[i]._enabled) { - isAuto = false - break - } - } - - // Interact with ME - if (isAuto) { - this.hls.currentLevel = -1 - return - } - - // Find ID of highest enabled track - let selectedTrack: number - - for (selectedTrack = qualityLevels.length - 1; selectedTrack >= 0; selectedTrack--) { - if (qualityLevels[selectedTrack]._enabled) { - break - } - } - - this.hls.currentLevel = selectedTrack - } - - private _handleQualityLevels () { - if (!this.metadata) return - - const qualityLevels = this.player.qualityLevels?.() - if (!qualityLevels) return - - for (let i = 0; i < this.metadata.levels.length; i++) { - const details = this.metadata.levels[i] - const representation: QualityLevelRepresentation = { - id: i, - width: details.width, - height: details.height, - bandwidth: details.bitrate, - bitrate: details.bitrate, - _enabled: true - } - - const self = this - representation.enabled = function (this: QualityLevels, level: number, toggle?: boolean) { - // Brightcove switcher works TextTracks-style (enable tracks that it wants to ABR on) - if (typeof toggle === 'boolean') { - this[level]._enabled = toggle - self._relayQualityChange(this) - } - - return this[level]._enabled - } - - qualityLevels.addQualityLevel(representation) - } + return '0' } private _notifyVideoQualities () { if (!this.metadata) return - const cleanTracklist = [] - if (this.metadata.levels.length > 1) { - const autoLevel = { - id: -1, - label: 'auto', - selected: this.hls.manualLevel === -1 - } - cleanTracklist.push(autoLevel) - } + const resolutions: PeerTubeResolution[] = [] this.metadata.levels.forEach((level, index) => { - // Don't write in level (shared reference with Hls.js) - const quality = { + resolutions.push({ id: index, - selected: index === this.hls.manualLevel, - label: this._levelLabel(level) - } - - cleanTracklist.push(quality) + height: level.height, + width: level.width, + bitrate: level.bitrate, + label: this.buildLevelLabel(level), + selected: level.id === this.hls.manualLevel, + + selectCallback: () => { + this.hls.currentLevel = index + } + }) }) - const payload = { - qualityData: { video: cleanTracklist }, - qualitySwitchCallback: this.switchQuality.bind(this) - } - - this.tech.trigger('loadedqualitydata', payload) - - // Self-de-register so we don't raise the payload multiple times - this.videoElement.removeEventListener('playing', this.handlers.playing) - } - - private _updateSelectedAudioTrack () { - const playerAudioTracks = this.tech.audioTracks() - for (let j = 0; j < playerAudioTracks.length; j++) { - // FIXME: typings - if ((playerAudioTracks[j] as any).enabled) { - this.hls.audioTrack = j - break - } - } - } - - private _onAudioTracks () { - const hlsAudioTracks = this.hls.audioTracks - const playerAudioTracks = this.tech.audioTracks() - - if (hlsAudioTracks.length > 1 && playerAudioTracks.length === 0) { - // Add Hls.js audio tracks if not added yet - for (let i = 0; i < hlsAudioTracks.length; i++) { - playerAudioTracks.addTrack(new this.vjs.AudioTrack({ - id: i.toString(), - kind: 'alternative', - label: hlsAudioTracks[i].name || hlsAudioTracks[i].lang, - language: hlsAudioTracks[i].lang, - enabled: i === this.hls.audioTrack - })) - } - - // Handle audio track change event - this.handlers.audioTracksChange = this._updateSelectedAudioTrack.bind(this) - playerAudioTracks.addEventListener('change', this.handlers.audioTracksChange) - } - } - - private _getTextTrackLabel (textTrack: TextTrack) { - // Label here is readable label and is optional (used in the UI so if it is there it should be different) - return textTrack.label ? textTrack.label : textTrack.language - } - - private _isSameTextTrack (track1: TextTrack, track2: TextTrack) { - return this._getTextTrackLabel(track1) === this._getTextTrackLabel(track2) && - track1.kind === track2.kind - } - - private _updateSelectedTextTrack () { - const playerTextTracks = this.player.textTracks() - let activeTrack: TextTrack = null - - for (let j = 0; j < playerTextTracks.length; j++) { - if (playerTextTracks[j].mode === 'showing') { - activeTrack = playerTextTracks[j] - break - } - } + resolutions.push({ + id: -1, + label: this.player.localize('Auto'), + selected: true, + selectCallback: () => this.hls.currentLevel = -1 + }) - const hlsjsTracks = this.videoElement.textTracks - for (let k = 0; k < hlsjsTracks.length; k++) { - if (hlsjsTracks[k].kind === 'subtitles' || hlsjsTracks[k].kind === 'captions') { - hlsjsTracks[k].mode = activeTrack && this._isSameTextTrack(hlsjsTracks[k], activeTrack) - ? 'showing' - : 'disabled' - } - } + this.player.peertubeResolutions().add(resolutions) } private _startLoad () { @@ -472,97 +329,10 @@ class Html5Hlsjs { return result } - private _filterDisplayableTextTracks (textTracks: TextTrackList) { - const displayableTracks = [] - - // Filter out tracks that is displayable (captions or subtitles) - for (let idx = 0; idx < textTracks.length; idx++) { - if (textTracks[idx].kind === 'subtitles' || textTracks[idx].kind === 'captions') { - displayableTracks.push(textTracks[idx]) - } - } - - return displayableTracks - } - - private _updateTextTrackList () { - const displayableTracks = this._filterDisplayableTextTracks(this.videoElement.textTracks) - const playerTextTracks = this.player.textTracks() - - // Add stubs to make the caption switcher shows up - // Adding the Hls.js text track in will make us have double captions - for (let idx = 0; idx < displayableTracks.length; idx++) { - let isAdded = false - - for (let jdx = 0; jdx < playerTextTracks.length; jdx++) { - if (this._isSameTextTrack(displayableTracks[idx], playerTextTracks[jdx])) { - isAdded = true - break - } - } - - if (!isAdded) { - const hlsjsTextTrack = displayableTracks[idx] - this.player.addRemoteTextTrack({ - kind: hlsjsTextTrack.kind as videojs.TextTrack.Kind, - label: this._getTextTrackLabel(hlsjsTextTrack), - language: hlsjsTextTrack.language, - srclang: hlsjsTextTrack.language - }, false) - } - } - - // Handle UI switching - this._updateSelectedTextTrack() - - if (!this.uiTextTrackHandled) { - this.handlers.textTracksChange = this._updateSelectedTextTrack.bind(this) - playerTextTracks.addEventListener('change', this.handlers.textTracksChange) - - this.uiTextTrackHandled = true - } - } - - private _onMetaData (_event: any, data: ManifestLoadedData) { + private _onMetaData (_event: any, data: ManifestParsedData) { // This could arrive before 'loadedqualitydata' handlers is registered, remember it so we can raise it later - this.metadata = data as any - this._handleQualityLevels() - } - - private _createCueHandler (captionConfig: any) { - return { - newCue: (track: any, startTime: number, endTime: number, captionScreen: { rows: any[] }) => { - let row: any - let cue: VTTCue - let text: string - const VTTCue = (window as any).VTTCue || (window as any).TextTrackCue - - for (let r = 0; r < captionScreen.rows.length; r++) { - row = captionScreen.rows[r] - text = '' - - if (!row.isEmpty()) { - for (let c = 0; c < row.chars.length; c++) { - text += row.chars[c].ucharj - } - - cue = new VTTCue(startTime, endTime, text.trim()) - - // typeof null === 'object' - if (captionConfig != null && typeof captionConfig === 'object') { - // Copy client overridden property into the cue object - const configKeys = Object.keys(captionConfig) - - for (let k = 0; k < configKeys.length; k++) { - cue[configKeys[k]] = captionConfig[configKeys[k]] - } - } - track.addCue(cue) - if (endTime === startTime) track.addCue(new VTTCue(endTime + 5, '')) - } - } - } - } + this.metadata = data + this._notifyVideoQualities() } private _initHlsjs () { @@ -577,11 +347,6 @@ class Html5Hlsjs { this.hlsjsConfig.autoStartLoad = false } - const captionConfig = srOptions_?.captionConfig || techOptions.captionConfig - if (captionConfig) { - this.hlsjsConfig.cueHandler = this._createCueHandler(captionConfig) - } - // If the user explicitly sets autoStartLoad to false, we're not going to enter the if block above // That's why we have a separate if block here to set the 'play' listener if (this.hlsjsConfig.autoStartLoad === false) { @@ -589,17 +354,12 @@ class Html5Hlsjs { this.videoElement.addEventListener('play', this.handlers.play) } - // _notifyVideoQualities sometimes runs before the quality picker event handler is registered -> no video switcher - this.handlers.playing = this._notifyVideoQualities.bind(this) - this.videoElement.addEventListener('playing', this.handlers.playing) - this.hls = new Hlsjs(this.hlsjsConfig) this._executeHooksFor('beforeinitialize') this.hls.on(Hlsjs.Events.ERROR, (event, data) => this._onError(event, data)) - this.hls.on(Hlsjs.Events.AUDIO_TRACKS_UPDATED, () => this._onAudioTracks()) - this.hls.on(Hlsjs.Events.MANIFEST_PARSED, (event, data) => this._onMetaData(event, data as any)) // FIXME: typings + this.hls.on(Hlsjs.Events.MANIFEST_PARSED, (event, data) => this._onMetaData(event, data)) this.hls.on(Hlsjs.Events.LEVEL_LOADED, (event, data) => { // The DVR plugin will auto seek to "live edge" on start up if (this.hlsjsConfig.liveSyncDuration) { @@ -612,12 +372,25 @@ class Html5Hlsjs { this.dvrDuration = data.details.totalduration this._duration = this.isLive ? Infinity : data.details.totalduration }) + this.hls.once(Hlsjs.Events.FRAG_LOADED, () => { // Emit custom 'loadedmetadata' event for parity with `videojs-contrib-hls` // Ref: https://github.com/videojs/videojs-contrib-hls#loadedmetadata this.tech.trigger('loadedmetadata') }) + this.hls.on(Hlsjs.Events.LEVEL_SWITCHING, (_e, data: LevelSwitchingData) => { + const resolutionId = this.hls.autoLevelEnabled + ? -1 + : data.level + + const autoResolutionChosenId = this.hls.autoLevelEnabled + ? data.level + : -1 + + this.player.peertubeResolutions().select({ id: resolutionId, autoResolutionChosenId, byEngine: true }) + }) + this.hls.attachMedia(this.videoElement) this.hls.loadSource(this.source.src) diff --git a/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts b/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts index acd40636e..d917fda03 100644 --- a/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts +++ b/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts @@ -116,14 +116,6 @@ class P2pMediaLoaderPlugin extends Plugin { const options = this.player.tech(true).options_ as any this.p2pEngine = options.hlsjsConfig.loader.getEngine() - this.hlsjs.on(Hlsjs.Events.LEVEL_SWITCHING, (_: any, data: any) => { - this.trigger('resolutionChange', { auto: this.hlsjs.autoLevelEnabled, resolutionId: data.height }) - }) - - this.hlsjs.on(Hlsjs.Events.MANIFEST_LOADED, (_: any, data: any) => { - this.trigger('resolutionsLoaded') - }) - this.p2pEngine.on(Events.SegmentError, (segment: Segment, err) => { console.error('Segment error.', segment, err) diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts index f3c21fc4c..230d6298b 100644 --- a/client/src/assets/player/peertube-player-manager.ts +++ b/client/src/assets/player/peertube-player-manager.ts @@ -1,13 +1,13 @@ import 'videojs-hotkeys/videojs.hotkeys' import 'videojs-dock' import 'videojs-contextmenu-pt' -import 'videojs-contrib-quality-levels' import './upnext/end-card' import './upnext/upnext-plugin' import './stats/stats-card' import './stats/stats-plugin' import './bezels/bezels-plugin' import './peertube-plugin' +import './peertube-resolutions-plugin' import './videojs-components/next-previous-video-button' import './videojs-components/p2p-info-button' import './videojs-components/peertube-link-button' diff --git a/client/src/assets/player/peertube-plugin.ts b/client/src/assets/player/peertube-plugin.ts index b4841b235..9b4dc9bd5 100644 --- a/client/src/assets/player/peertube-plugin.ts +++ b/client/src/assets/player/peertube-plugin.ts @@ -1,4 +1,3 @@ -import './videojs-components/settings-menu-button' import videojs from 'video.js' import { timeToInt } from '@shared/core-utils' import { @@ -10,7 +9,7 @@ import { saveVideoWatchHistory, saveVolumeInStore } from './peertube-player-local-storage' -import { PeerTubePluginOptions, ResolutionUpdateData, UserWatching, VideoJSCaption } from './peertube-videojs-typings' +import { PeerTubePluginOptions, UserWatching, VideoJSCaption } from './peertube-videojs-typings' import { isMobile } from './utils' const Plugin = videojs.getPlugin('plugin') @@ -27,7 +26,6 @@ class PeerTubePlugin extends Plugin { private videoViewInterval: any private userWatchingVideoInterval: any - private lastResolutionChange: ResolutionUpdateData private isLive: boolean @@ -54,22 +52,6 @@ class PeerTubePlugin extends Plugin { this.player.ready(() => { const playerOptions = this.player.options_ - if (options.mode === 'webtorrent') { - this.player.webtorrent().on('resolutionChange', (_: any, d: any) => this.handleResolutionChange(d)) - this.player.webtorrent().on('autoResolutionChange', (_: any, d: any) => this.trigger('autoResolutionChange', d)) - } - - if (options.mode === 'p2p-media-loader') { - this.player.p2pMediaLoader().on('resolutionChange', (_: any, d: any) => this.handleResolutionChange(d)) - } - - this.player.tech(true).on('loadedqualitydata', () => { - setTimeout(() => { - // Replay a resolution change, now we loaded all quality data - if (this.lastResolutionChange) this.handleResolutionChange(this.lastResolutionChange) - }, 0) - }) - const volume = getStoredVolume() if (volume !== undefined) this.player.volume(volume) @@ -97,7 +79,7 @@ class PeerTubePlugin extends Plugin { }) } - this.player.textTracks().on('change', () => { + this.player.textTracks().addEventListener('change', () => { const showing = this.player.textTracks().tracks_.find(t => { return t.kind === 'captions' && t.mode === 'showing' }) @@ -216,22 +198,6 @@ class PeerTubePlugin extends Plugin { return fetch(url, { method: 'PUT', body, headers }) } - private handleResolutionChange (data: ResolutionUpdateData) { - this.lastResolutionChange = data - - const qualityLevels = this.player.qualityLevels() - - for (let i = 0; i < qualityLevels.length; i++) { - if (qualityLevels[i].height === data.resolutionId) { - data.id = qualityLevels[i].id - break - } - } - - console.log('Resolution changed.', data) - this.trigger('resolutionChange', data) - } - private listenControlBarMouse () { this.player.controlBar.on('mouseenter', () => { this.mouseInControlBar = true diff --git a/client/src/assets/player/peertube-resolutions-plugin.ts b/client/src/assets/player/peertube-resolutions-plugin.ts new file mode 100644 index 000000000..cc36f18f3 --- /dev/null +++ b/client/src/assets/player/peertube-resolutions-plugin.ts @@ -0,0 +1,88 @@ +import videojs from 'video.js' +import { PeerTubeResolution } from './peertube-videojs-typings' + +const Plugin = videojs.getPlugin('plugin') + +class PeerTubeResolutionsPlugin extends Plugin { + private currentSelection: PeerTubeResolution + private resolutions: PeerTubeResolution[] = [] + + private autoResolutionChosenId: number + private autoResolutionEnabled = true + + add (resolutions: PeerTubeResolution[]) { + for (const r of resolutions) { + this.resolutions.push(r) + } + + this.currentSelection = this.getSelected() + + this.sort() + this.trigger('resolutionsAdded') + } + + getResolutions () { + return this.resolutions + } + + getSelected () { + return this.resolutions.find(r => r.selected) + } + + getAutoResolutionChosen () { + return this.resolutions.find(r => r.id === this.autoResolutionChosenId) + } + + select (options: { + id: number + byEngine: boolean + autoResolutionChosenId?: number + }) { + const { id, autoResolutionChosenId, byEngine } = options + + if (this.currentSelection?.id === id && this.autoResolutionChosenId === autoResolutionChosenId) return + + this.autoResolutionChosenId = autoResolutionChosenId + + for (const r of this.resolutions) { + r.selected = r.id === id + + if (r.selected) { + this.currentSelection = r + + if (!byEngine) r.selectCallback() + } + } + + this.trigger('resolutionChanged') + } + + disableAutoResolution () { + this.autoResolutionEnabled = false + this.trigger('autoResolutionEnabledChanged') + } + + enabledAutoResolution () { + this.autoResolutionEnabled = true + this.trigger('autoResolutionEnabledChanged') + } + + isAutoResolutionEnabeld () { + return this.autoResolutionEnabled + } + + private sort () { + this.resolutions.sort((a, b) => { + if (a.id === -1) return 1 + if (b.id === -1) return -1 + + if (a.height > b.height) return -1 + if (a.height === b.height) return 0 + return 1 + }) + } + +} + +videojs.registerPlugin('peertubeResolutions', PeerTubeResolutionsPlugin) +export { PeerTubeResolutionsPlugin } diff --git a/client/src/assets/player/peertube-videojs-typings.ts b/client/src/assets/player/peertube-videojs-typings.ts index 97828c802..bd6db4ffc 100644 --- a/client/src/assets/player/peertube-videojs-typings.ts +++ b/client/src/assets/player/peertube-videojs-typings.ts @@ -1,6 +1,3 @@ -// FIXME: lint -/* eslint-disable @typescript-eslint/ban-types */ - import { HlsConfig, Level } from 'hls.js' import videojs from 'video.js' import { VideoFile, VideoPlaylist, VideoPlaylistElement } from '@shared/models' @@ -8,11 +5,12 @@ import { P2pMediaLoaderPlugin } from './p2p-media-loader/p2p-media-loader-plugin import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager' import { PlayerMode } from './peertube-player-manager' import { PeerTubePlugin } from './peertube-plugin' +import { PeerTubeResolutionsPlugin } from './peertube-resolutions-plugin' import { PlaylistPlugin } from './playlist/playlist-plugin' -import { EndCardOptions } from './upnext/end-card' import { StatsCardOptions } from './stats/stats-card' -import { WebTorrentPlugin } from './webtorrent/webtorrent-plugin' import { StatsForNerdsPlugin } from './stats/stats-plugin' +import { EndCardOptions } from './upnext/end-card' +import { WebTorrentPlugin } from './webtorrent/webtorrent-plugin' declare module 'video.js' { @@ -37,16 +35,15 @@ declare module 'video.js' { p2pMediaLoader (): P2pMediaLoaderPlugin + peertubeResolutions (): PeerTubeResolutionsPlugin + contextmenuUI (options: any): any bezels (): void stats (options?: StatsCardOptions): StatsForNerdsPlugin - qualityLevels (): QualityLevels - textTracks (): TextTrackList & { - on: Function tracks_: (TextTrack & { id: string, label: string, src: string })[] } @@ -69,24 +66,16 @@ export interface HlsjsConfigHandlerOptions { levelLabelHandler?: (level: Level) => string } -type QualityLevelRepresentation = { +type PeerTubeResolution = { id: number - height: number + height?: number label?: string width?: number - bandwidth?: number bitrate?: number - enabled?: Function - _enabled: boolean -} - -type QualityLevels = QualityLevelRepresentation[] & { - selectedIndex: number - selectedIndex_: number - - addQualityLevel (representation: QualityLevelRepresentation): void + selected: boolean + selectCallback: () => void } type VideoJSCaption = { @@ -131,7 +120,7 @@ type PlaylistPluginOptions = { type NextPreviousVideoButtonOptions = { type: 'next' | 'previous' - handler: Function + handler: () => void isDisabled: () => boolean } @@ -214,7 +203,7 @@ type PlayerNetworkInfo = { type PlaylistItemOptions = { element: VideoPlaylistElement - onClicked: Function + onClicked: () => void } export { @@ -229,9 +218,8 @@ export { PeerTubePluginOptions, WebtorrentPluginOptions, P2PMediaLoaderPluginOptions, + PeerTubeResolution, VideoJSPluginOptions, LoadedQualityData, - QualityLevelRepresentation, - PeerTubeLinkButtonOptions, - QualityLevels + PeerTubeLinkButtonOptions } diff --git a/client/src/assets/player/videojs-components/resolution-menu-button.ts b/client/src/assets/player/videojs-components/resolution-menu-button.ts index 98e7f56fc..8bd5b4f03 100644 --- a/client/src/assets/player/videojs-components/resolution-menu-button.ts +++ b/client/src/assets/player/videojs-components/resolution-menu-button.ts @@ -1,6 +1,4 @@ import videojs from 'video.js' - -import { LoadedQualityData } from '../peertube-videojs-typings' import { ResolutionMenuItem } from './resolution-menu-item' const Menu = videojs.getComponent('Menu') @@ -13,9 +11,12 @@ class ResolutionMenuButton extends MenuButton { this.controlText('Quality') - player.tech(true).on('loadedqualitydata', (e: any, data: any) => this.buildQualities(data)) + player.peertubeResolutions().on('resolutionsAdded', () => this.buildQualities()) - player.peertube().on('resolutionChange', () => setTimeout(() => this.trigger('updateLabel'), 0)) + // For parent + player.peertubeResolutions().on('resolutionChanged', () => { + setTimeout(() => this.trigger('labelUpdated')) + }) } createEl () { @@ -58,20 +59,8 @@ class ResolutionMenuButton extends MenuButton { }) } - private buildQualities (data: LoadedQualityData) { - // The automatic resolution item will need other labels - const labels: { [ id: number ]: string } = {} - - data.qualityData.video.sort((a, b) => { - if (a.id > b.id) return -1 - if (a.id === b.id) return 0 - return 1 - }) - - for (const d of data.qualityData.video) { - // Skip auto resolution, we'll add it ourselves - if (d.id === -1) continue - + private buildQualities () { + for (const d of this.player().peertubeResolutions().getResolutions()) { const label = d.label === '0p' ? this.player().localize('Audio-only') : d.label @@ -81,25 +70,11 @@ class ResolutionMenuButton extends MenuButton { { id: d.id, label, - selected: d.selected, - callback: data.qualitySwitchCallback + selected: d.selected }) ) - - labels[d.id] = d.label } - this.menu.addChild(new ResolutionMenuItem( - this.player_, - { - id: -1, - label: this.player_.localize('Auto'), - labels, - callback: data.qualitySwitchCallback, - selected: true // By default, in auto mode - } - )) - for (const m of this.menu.children()) { this.addClickListener(m) } diff --git a/client/src/assets/player/videojs-components/resolution-menu-item.ts b/client/src/assets/player/videojs-components/resolution-menu-item.ts index c1f502600..6047f52f7 100644 --- a/client/src/assets/player/videojs-components/resolution-menu-item.ts +++ b/client/src/assets/player/videojs-components/resolution-menu-item.ts @@ -1,82 +1,72 @@ import videojs from 'video.js' -import { AutoResolutionUpdateData, ResolutionUpdateData } from '../peertube-videojs-typings' const MenuItem = videojs.getComponent('MenuItem') export interface ResolutionMenuItemOptions extends videojs.MenuItemOptions { - labels?: { [id: number]: string } id: number - callback: (resolutionId: number, type: 'video') => void } class ResolutionMenuItem extends MenuItem { private readonly resolutionId: number private readonly label: string - // Only used for the automatic item - private readonly labels: { [id: number]: string } - private readonly callback: (resolutionId: number, type: 'video') => void - private autoResolutionPossible: boolean - private currentResolutionLabel: string + private autoResolutionEnabled: boolean + private autoResolutionChosen: string constructor (player: videojs.Player, options?: ResolutionMenuItemOptions) { options.selectable = true super(player, options) - this.autoResolutionPossible = true - this.currentResolutionLabel = '' + this.autoResolutionEnabled = true + this.autoResolutionChosen = '' this.resolutionId = options.id this.label = options.label - this.labels = options.labels - this.callback = options.callback - player.peertube().on('resolutionChange', (_: any, data: ResolutionUpdateData) => this.updateSelection(data)) + player.peertubeResolutions().on('resolutionChanged', () => this.updateSelection()) // We only want to disable the "Auto" item if (this.resolutionId === -1) { - player.peertube().on('autoResolutionChange', (_: any, data: AutoResolutionUpdateData) => this.updateAutoResolution(data)) + player.peertubeResolutions().on('autoResolutionEnabledChanged', () => this.updateAutoResolution()) } } handleClick (event: any) { // Auto button disabled? - if (this.autoResolutionPossible === false && this.resolutionId === -1) return + if (this.autoResolutionEnabled === false && this.resolutionId === -1) return super.handleClick(event) - this.callback(this.resolutionId, 'video') + this.player().peertubeResolutions().select({ id: this.resolutionId, byEngine: false }) } - updateSelection (data: ResolutionUpdateData) { - if (this.resolutionId === -1) { - this.currentResolutionLabel = this.labels[data.id] - } + updateSelection () { + const selectedResolution = this.player().peertubeResolutions().getSelected() - // Automatic resolution only - if (data.auto === true) { - this.selected(this.resolutionId === -1) - return + if (this.resolutionId === -1) { + this.autoResolutionChosen = this.player().peertubeResolutions().getAutoResolutionChosen()?.label } - this.selected(this.resolutionId === data.id) + this.selected(this.resolutionId === selectedResolution.id) } - updateAutoResolution (data: AutoResolutionUpdateData) { + updateAutoResolution () { + const enabled = this.player().peertubeResolutions().isAutoResolutionEnabeld() + // Check if the auto resolution is enabled or not - if (data.possible === false) { + if (enabled === false) { this.addClass('disabled') } else { this.removeClass('disabled') } - this.autoResolutionPossible = data.possible + this.autoResolutionEnabled = enabled } getLabel () { if (this.resolutionId === -1) { - return this.label + ' ' + this.currentResolutionLabel + '' + return this.label + ' ' + this.autoResolutionChosen + '' } return this.label diff --git a/client/src/assets/player/videojs-components/settings-menu-item.ts b/client/src/assets/player/videojs-components/settings-menu-item.ts index 1871d41f8..48fed0fd9 100644 --- a/client/src/assets/player/videojs-components/settings-menu-item.ts +++ b/client/src/assets/player/videojs-components/settings-menu-item.ts @@ -248,7 +248,7 @@ class SettingsMenuItem extends MenuItem { } build () { - this.subMenu.on('updateLabel', () => { + this.subMenu.on('labelUpdated', () => { this.update() }) this.subMenu.on('menuChanged', () => { diff --git a/client/src/assets/player/webtorrent/webtorrent-plugin.ts b/client/src/assets/player/webtorrent/webtorrent-plugin.ts index 0587ddee6..a464f02d5 100644 --- a/client/src/assets/player/webtorrent/webtorrent-plugin.ts +++ b/client/src/assets/player/webtorrent/webtorrent-plugin.ts @@ -9,7 +9,7 @@ import { getStoredVolume, saveAverageBandwidth } from '../peertube-player-local-storage' -import { LoadedQualityData, PlayerNetworkInfo, WebtorrentPluginOptions } from '../peertube-videojs-typings' +import { PeerTubeResolution, PlayerNetworkInfo, WebtorrentPluginOptions } from '../peertube-videojs-typings' import { getRtcConfig, isIOS, videoFileMaxByResolution, videoFileMinByResolution } from '../utils' import { PeertubeChunkStore } from './peertube-chunk-store' import { renderVideo } from './video-renderer' @@ -175,8 +175,7 @@ class WebTorrentPlugin extends Plugin { return done() }) - this.changeQuality() - this.trigger('resolutionChange', { auto: this.autoResolution, resolutionId: this.currentVideoFile.resolution.id }) + this.selectAppropriateResolution(true) } updateResolution (resolutionId: number, delay = 0) { @@ -219,17 +218,10 @@ class WebTorrentPlugin extends Plugin { } } - enableAutoResolution () { - this.autoResolution = true - this.trigger('resolutionChange', { auto: this.autoResolution, resolutionId: this.getCurrentResolutionId() }) - } - - disableAutoResolution (forbid = false) { - if (forbid === true) this.autoResolutionPossible = false - + disableAutoResolution () { this.autoResolution = false - this.trigger('autoResolutionChange', { possible: this.autoResolutionPossible }) - this.trigger('resolutionChange', { auto: this.autoResolution, resolutionId: this.getCurrentResolutionId() }) + this.autoResolutionPossible = false + this.player.peertubeResolutions().disableAutoResolution() } isAutoResolutionPossible () { @@ -516,7 +508,7 @@ class WebTorrentPlugin extends Plugin { private fallbackToHttp (options: PlayOptions, done?: (err?: Error) => void) { const paused = this.player.paused() - this.disableAutoResolution(true) + this.disableAutoResolution() this.flushVideoFile(this.currentVideoFile, true) this.torrent = null @@ -528,7 +520,7 @@ class WebTorrentPlugin extends Plugin { this.player.src = this.savePlayerSrcFunction this.player.src(httpUrl) - this.changeQuality() + this.selectAppropriateResolution(true) // We changed the source, so reinit captions this.player.trigger('sourcechange') @@ -601,32 +593,22 @@ class WebTorrentPlugin extends Plugin { } private buildQualities () { - const qualityLevelsPayload = [] - - for (const file of this.videoFiles) { - const representation = { - id: file.resolution.id, - label: this.buildQualityLabel(file), - height: file.resolution.id, - _enabled: true - } - - this.player.qualityLevels().addQualityLevel(representation) - - qualityLevelsPayload.push({ - id: representation.id, - label: representation.label, - selected: false - }) - } + const resolutions: PeerTubeResolution[] = this.videoFiles.map(file => ({ + id: file.resolution.id, + label: this.buildQualityLabel(file), + height: file.resolution.id, + selected: false, + selectCallback: () => this.qualitySwitchCallback(file.resolution.id) + })) + + resolutions.push({ + id: -1, + label: this.player.localize('Auto'), + selected: true, + selectCallback: () => this.qualitySwitchCallback(-1) + }) - const payload: LoadedQualityData = { - qualitySwitchCallback: (d: any) => this.qualitySwitchCallback(d), - qualityData: { - video: qualityLevelsPayload - } - } - this.player.tech(true).trigger('loadedqualitydata', payload) + this.player.peertubeResolutions().add(resolutions) } private buildQualityLabel (file: VideoFile) { @@ -641,27 +623,30 @@ class WebTorrentPlugin extends Plugin { private qualitySwitchCallback (id: number) { if (id === -1) { - if (this.autoResolutionPossible === true) this.enableAutoResolution() + if (this.autoResolutionPossible === true) { + this.autoResolution = true + + this.selectAppropriateResolution(false) + } + return } - this.disableAutoResolution() + this.autoResolution = false this.updateResolution(id) + this.selectAppropriateResolution(false) } - private changeQuality () { - const resolutionId = this.currentVideoFile.resolution.id as number - const qualityLevels = this.player.qualityLevels() + private selectAppropriateResolution (byEngine: boolean) { + const resolution = this.autoResolution + ? -1 + : this.getCurrentResolutionId() - if (resolutionId === -1) { - qualityLevels.selectedIndex = -1 - return - } + const autoResolutionChosen = this.autoResolution + ? this.getCurrentResolutionId() + : undefined - for (let i = 0; i < qualityLevels.length; i++) { - const q = qualityLevels[i] - if (q.height === resolutionId) qualityLevels.selectedIndex_ = i - } + this.player.peertubeResolutions().select({ id: resolution, autoResolutionChosenId: autoResolutionChosen, byEngine }) } } diff --git a/client/src/standalone/videos/embed-api.ts b/client/src/standalone/videos/embed-api.ts index b5c9da431..8443cb7e9 100644 --- a/client/src/standalone/videos/embed-api.ts +++ b/client/src/standalone/videos/embed-api.ts @@ -139,15 +139,8 @@ export class PeerTubeEmbedApi { }) // PeerTube specific capabilities - if (this.isWebtorrent()) { - this.embed.player.webtorrent().on('autoResolutionUpdate', () => this.loadWebTorrentResolutions()) - this.embed.player.webtorrent().on('videoFileUpdate', () => this.loadWebTorrentResolutions()) - - this.loadWebTorrentResolutions() - } else { - this.embed.player.p2pMediaLoader().on('resolutionChange', () => this.loadP2PMediaLoaderResolutions()) - this.embed.player.p2pMediaLoader().on('resolutionsLoaded', () => this.loadP2PMediaLoaderResolutions()) - } + this.embed.player.peertubeResolutions().on('resolutionsAdded', () => this.loadResolutions()) + this.embed.player.peertubeResolutions().on('resolutionChanged', () => this.loadResolutions()) this.embed.player.on('volumechange', () => { this.channel.notify({ @@ -183,23 +176,15 @@ export class PeerTubeEmbedApi { }) } - private loadP2PMediaLoaderResolutions () { - this.resolutions = [] - - const qualityLevels = this.embed.player.qualityLevels() - const currentResolutionId = this.embed.player.qualityLevels().selectedIndex - - for (let i = 0; i < qualityLevels.length; i++) { - const level = qualityLevels[i] - - this.resolutions.push({ - id: level.id, - label: level.height + 'p', - active: level.id === currentResolutionId, - width: level.width, - height: level.height - }) - } + private loadResolutions () { + this.resolutions = this.embed.player.peertubeResolutions().getResolutions() + .map(r => ({ + id: r.id, + label: r.height + 'p', + active: r.selected, + width: r.width, + height: r.height + })) this.channel.notify({ method: 'resolutionUpdate', diff --git a/client/yarn.lock b/client/yarn.lock index 8f41b3102..508b8ad28 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -12618,14 +12618,6 @@ videojs-contextmenu-pt@^5.4.1: global "^4.4.0" video.js "^7.6.0" -videojs-contrib-quality-levels@^2.0.9: - version "2.1.0" - resolved "https://registry.yarnpkg.com/videojs-contrib-quality-levels/-/videojs-contrib-quality-levels-2.1.0.tgz#046e9e21ed01043f512b83a1916001d552457083" - integrity sha512-dqGQGbL9AFhucxki7Zh0c3kIhH0PAPcHEh6jUdRyaFCVeOuqnJrOYs/3wNtsokDdBdRf2Du2annpu4Z2XaSZRg== - dependencies: - global "^4.3.2" - video.js "^6 || ^7" - videojs-dock@^2.0.2: version "2.2.0" resolved "https://registry.yarnpkg.com/videojs-dock/-/videojs-dock-2.2.0.tgz#57e4f942da1b8e930c4387fed85942473bc40829" -- cgit v1.2.3 From 89ac282e0437ae839e3d71566f25321f9b9384e9 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 10 Sep 2021 15:43:15 +0200 Subject: Fix embed api --- .../assets/player/webtorrent/webtorrent-plugin.ts | 40 ++++++++++----------- client/src/standalone/videos/embed-api.ts | 41 +++------------------- client/src/standalone/videos/test-embed.ts | 2 -- 3 files changed, 25 insertions(+), 58 deletions(-) (limited to 'client') diff --git a/client/src/assets/player/webtorrent/webtorrent-plugin.ts b/client/src/assets/player/webtorrent/webtorrent-plugin.ts index a464f02d5..1a1cd7f1a 100644 --- a/client/src/assets/player/webtorrent/webtorrent-plugin.ts +++ b/client/src/assets/player/webtorrent/webtorrent-plugin.ts @@ -178,7 +178,7 @@ class WebTorrentPlugin extends Plugin { this.selectAppropriateResolution(true) } - updateResolution (resolutionId: number, delay = 0) { + updateEngineResolution (resolutionId: number, delay = 0) { // Remember player state const currentTime = this.player.currentTime() const isPaused = this.player.paused() @@ -236,6 +236,22 @@ class WebTorrentPlugin extends Plugin { return this.currentVideoFile } + changeQuality (id: number) { + if (id === -1) { + if (this.autoResolutionPossible === true) { + this.autoResolution = true + + this.selectAppropriateResolution(false) + } + + return + } + + this.autoResolution = false + this.updateEngineResolution(id) + this.selectAppropriateResolution(false) + } + private addTorrent ( magnetOrTorrentUrl: string, previousVideoFile: VideoFile, @@ -458,7 +474,7 @@ class WebTorrentPlugin extends Plugin { } if (changeResolution === true) { - this.updateResolution(file.resolution.id, changeResolutionDelay) + this.updateEngineResolution(file.resolution.id, changeResolutionDelay) // Wait some seconds in observation of our new resolution this.isAutoResolutionObservation = true @@ -598,14 +614,14 @@ class WebTorrentPlugin extends Plugin { label: this.buildQualityLabel(file), height: file.resolution.id, selected: false, - selectCallback: () => this.qualitySwitchCallback(file.resolution.id) + selectCallback: () => this.changeQuality(file.resolution.id) })) resolutions.push({ id: -1, label: this.player.localize('Auto'), selected: true, - selectCallback: () => this.qualitySwitchCallback(-1) + selectCallback: () => this.changeQuality(-1) }) this.player.peertubeResolutions().add(resolutions) @@ -621,22 +637,6 @@ class WebTorrentPlugin extends Plugin { return label } - private qualitySwitchCallback (id: number) { - if (id === -1) { - if (this.autoResolutionPossible === true) { - this.autoResolution = true - - this.selectAppropriateResolution(false) - } - - return - } - - this.autoResolution = false - this.updateResolution(id) - this.selectAppropriateResolution(false) - } - private selectAppropriateResolution (byEngine: boolean) { const resolution = this.autoResolution ? -1 diff --git a/client/src/standalone/videos/embed-api.ts b/client/src/standalone/videos/embed-api.ts index 8443cb7e9..a28aeeaef 100644 --- a/client/src/standalone/videos/embed-api.ts +++ b/client/src/standalone/videos/embed-api.ts @@ -64,19 +64,12 @@ export class PeerTubeEmbedApi { if (this.isWebtorrent()) { if (resolutionId === -1 && this.embed.player.webtorrent().isAutoResolutionPossible() === false) return - // Auto resolution - if (resolutionId === -1) { - this.embed.player.webtorrent().enableAutoResolution() - return - } - - this.embed.player.webtorrent().disableAutoResolution() - this.embed.player.webtorrent().updateResolution(resolutionId) + this.embed.player.webtorrent().changeQuality(resolutionId) return } - this.embed.player.p2pMediaLoader().getHLSJS().nextLevel = resolutionId + this.embed.player.p2pMediaLoader().getHLSJS().currentLevel = resolutionId } private getCaptions (): PeerTubeTextTrack[] { @@ -142,6 +135,8 @@ export class PeerTubeEmbedApi { this.embed.player.peertubeResolutions().on('resolutionsAdded', () => this.loadResolutions()) this.embed.player.peertubeResolutions().on('resolutionChanged', () => this.loadResolutions()) + this.loadResolutions() + this.embed.player.on('volumechange', () => { this.channel.notify({ method: 'volumeChange', @@ -150,37 +145,11 @@ export class PeerTubeEmbedApi { }) } - private loadWebTorrentResolutions () { - this.resolutions = [] - - const currentResolutionId = this.embed.player.webtorrent().getCurrentResolutionId() - - for (const videoFile of this.embed.player.webtorrent().videoFiles) { - let label = videoFile.resolution.label - if (videoFile.fps && videoFile.fps >= 50) { - label += videoFile.fps - } - - this.resolutions.push({ - id: videoFile.resolution.id, - label, - src: videoFile.magnetUri, - active: videoFile.resolution.id === currentResolutionId, - height: videoFile.resolution.id - }) - } - - this.channel.notify({ - method: 'resolutionUpdate', - params: this.resolutions - }) - } - private loadResolutions () { this.resolutions = this.embed.player.peertubeResolutions().getResolutions() .map(r => ({ id: r.id, - label: r.height + 'p', + label: r.label, active: r.selected, width: r.width, height: r.height diff --git a/client/src/standalone/videos/test-embed.ts b/client/src/standalone/videos/test-embed.ts index 301c95d7b..7213cbf8b 100644 --- a/client/src/standalone/videos/test-embed.ts +++ b/client/src/standalone/videos/test-embed.ts @@ -84,8 +84,6 @@ window.addEventListener('load', async () => { captionEl.innerHTML = '' captions.forEach(c => { - console.log(c) - if (c.mode === 'showing') { const itemEl = document.createElement('strong') itemEl.innerText = `${c.label} (active)` -- cgit v1.2.3