// @ts-nocheck import videojs from 'video.js' import 'videojs-contrib-quality-levels' import type { QualityLevelList, QualityLevel } from 'videojs-contrib-quality-levels' import i18n from '@/i18n' import { getSetting } from '@/settingsManager' export const createQualitySelector = (player: any) => { const qualityLevels: QualityLevelList = player.qualityLevels() const MenuButton = videojs.getComponent('MenuButton') const MenuItem = videojs.getComponent('MenuItem') let formatedQualities: { name: string; index: number; id: string; height: number }[] = [] let startupQualityApplied = false const t = i18n.global.t const translatedAuto = t('player.auto') const autoLabel = translatedAuto === 'player.auto' ? 'Auto' : translatedAuto const setAutoQuality = () => { for (const quality of qualityLevels.levels_) { // Keep video renditions enabled and skip the audio-only pseudo-level. quality.enabled = quality.height !== undefined } qualityLevels.selectedIndex_ = -1 qualityLevels.trigger({ type: 'change', selectedIndex: -1 }) player.play().catch(() => { // Ignore autoplay restrictions; user interaction can resume. }) } const setQuality = (id?: string) => { if (!id) { setAutoQuality() return } const found = formatedQualities.find((i) => i.id === id) if (!found) { setAutoQuality() return } for (const quality of qualityLevels.levels_) { if (quality.height === undefined) { quality.enabled = false continue } // Keep adaptive bitrate enabled up to the selected quality. quality.enabled = quality.height <= found.height } qualityLevels.selectedIndex_ = found?.index qualityLevels.trigger({ type: 'change', selectedIndex: found?.index }) player.play().catch(() => { // Ignore autoplay restrictions; user interaction can resume. }) } class CustomMenuButton extends MenuButton { options_: any player: any createItems() { const player = this.player() return this.options_.items.map((item: { name: string }) => { const qualitySelectorButton = new MenuItem(player, { label: item.name }) qualitySelectorButton.handleClick = (data) => { const qualityClicked = data.currentTarget.innerText if (qualityClicked === autoLabel) { setAutoQuality() return } const id = formatedQualities.find((i) => i.name === qualityClicked)?.id if (id) { setQuality(id) } } return qualitySelectorButton }) } buildCSSClass() { return `vjs-icon-cog ${super.buildCSSClass()}` } } videojs.registerComponent('CustomMenuButton', CustomMenuButton) const formattedLevels = [] const updateLevels = () => { player.controlBar.removeChild('CustomMenuButton') player.controlBar.addChild('CustomMenuButton', { title: t('player.quality'), items: [{ name: autoLabel }, ...formatedQualities] }) if (!startupQualityApplied) { startupQualityApplied = true const startingQuality = String(getSetting('defaultQuality')) const targetHeight = Number(startingQuality.replace(/[^0-9]/g, '')) if (!Number.isFinite(targetHeight) || targetHeight <= 0) { setAutoQuality() return } // Prefer exact/default match; otherwise keep auto mode so ABR can adapt. const id = formatedQualities.find((i) => i.height === targetHeight)?.id setQuality(id) } } qualityLevels.on('addqualitylevel', () => { formatedQualities = qualityLevels.levels_.map((quality: QualityLevel, index: number) => { let qualityFramerate = '' if (quality.frameRate > 30) { qualityFramerate = quality.frameRate } return { name: quality.height?.toString() + 'p' + qualityFramerate, index, id: quality.id, height: quality.height || 0 } }) // remove audio only const audioOnlyQuality = formatedQualities[formatedQualities.length - 1] if (audioOnlyQuality.name === 'undefinedp') { formatedQualities.splice(formatedQualities.length - 1, 1) } formattedLevels.push(formatedQualities) updateLevels() }) }