Files
safetwitch/src/assets/qualitySelector.ts
T

147 lines
4.2 KiB
TypeScript

// @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()
})
}