Revert commit

This reverts commit 3db6d76d64.
This commit is contained in:
Chocobozzz
2026-03-30 14:51:52 +02:00
parent bc02c1043b
commit 88a4edc71f
6 changed files with 107 additions and 225 deletions
@@ -19,7 +19,6 @@ import './shared/control-bar/peertube-live-display'
import './shared/control-bar/storyboard-plugin' import './shared/control-bar/storyboard-plugin'
import './shared/control-bar/theater-button' import './shared/control-bar/theater-button'
import './shared/control-bar/time-tooltip' import './shared/control-bar/time-tooltip'
import './shared/control-bar/chapters-seek-bar'
import './shared/dock/peertube-dock-component' import './shared/dock/peertube-dock-component'
import './shared/dock/peertube-dock-plugin' import './shared/dock/peertube-dock-plugin'
import './shared/hotkeys/peertube-hotkeys-plugin' import './shared/hotkeys/peertube-hotkeys-plugin'
@@ -6,6 +6,7 @@
$slider-height: 3px; $slider-height: 3px;
$slider-hover-height: 5px; $slider-hover-height: 5px;
$chapter-marker-size: 9px;
.vjs-peertube-skin.has-chapter { .vjs-peertube-skin.has-chapter {
.vjs-time-tooltip { .vjs-time-tooltip {
@@ -17,6 +18,29 @@ $slider-hover-height: 5px;
} }
} }
.vjs-chapter-marker {
position: absolute;
top: math.floor(math.div($chapter-marker-size - $slider-height, 2)) * -1;
height: $chapter-marker-size;
width: $chapter-marker-size;
border-radius: $chapter-marker-size;
margin-left: math.div($chapter-marker-size, 2) * -1;
background-color: #fff;
cursor: pointer;
transition: transform 50ms ease-in-out, border-color 50ms ease-in-out;
border: 2px solid transparent;
z-index: 101;
&:hover {
transform: scale(1.5);
border-color: pvar(--primary);
}
}
.video-js.vjs-peertube-skin .vjs-control-bar { .video-js.vjs-peertube-skin .vjs-control-bar {
--first-last-element-margin: 10px; --first-last-element-margin: 10px;
--icon-size: 26px; --icon-size: 26px;
@@ -248,7 +272,7 @@ $slider-hover-height: 5px;
} }
.vjs-slider { .vjs-slider {
background-color: transparent; background-color: rgba(255, 255, 255, 0.2);
.vjs-play-progress { .vjs-play-progress {
background: pvar(--pt-player-fg); background: pvar(--pt-player-fg);
@@ -278,27 +302,6 @@ $slider-hover-height: 5px;
.vjs-caption-toggle-control { .vjs-caption-toggle-control {
width: var(--button-width); width: var(--button-width);
} }
// ---------------------------------------------------------------------------
// Chapters
// ---------------------------------------------------------------------------
.vjs-progress .vjs-progress-holder .vjs-chapters-progress {
position: absolute;
left: 0;
right: 0;
top: 0;
height: 100%;
pointer-events: none;
}
.vjs-progress-holder .vjs-chapters-progress-part {
position: absolute;
top: 0;
height: 100%;
opacity: 0.35;
background-color: #fff;
}
} }
.vjs-peertube-skin.vjs-has-captions .vjs-caption-toggle-control { .vjs-peertube-skin.vjs-has-captions .vjs-caption-toggle-control {
@@ -1,45 +1,47 @@
import { VideoChapter } from '@peertube/peertube-models' import { VideoChapter } from '@peertube/peertube-models'
import videojs from 'video.js' import videojs from 'video.js'
import { ChaptersOptions, VideojsPlayer, VideojsPlugin } from '../../types' import { ChaptersOptions, VideojsPlayer, VideojsPlugin } from '../../types'
import ChaptersProgressBar from './chapters-progress-bar' import { ProgressBarMarkerComponent } from './progress-bar-marker-component'
const Plugin = videojs.getPlugin('plugin') as typeof VideojsPlugin const Plugin = videojs.getPlugin('plugin') as typeof VideojsPlugin
class ChaptersPlugin extends Plugin { class ChaptersPlugin extends Plugin {
declare private chapters: VideoChapter[] declare private chapters: VideoChapter[]
declare private progressBar: ChaptersProgressBar declare private markers: ProgressBarMarkerComponent[]
private activeChapter: VideoChapter private activeChapter: VideoChapter
constructor (player: VideojsPlayer, options: ChaptersOptions) { constructor (player: VideojsPlayer, options: ChaptersOptions) {
super(player) super(player)
this.markers = []
this.chapters = options.chapters this.chapters = options.chapters
this.player.ready(() => { this.player.ready(() => {
player.addClass('vjs-chapters') player.addClass('vjs-chapters')
this.progressBar = new ChaptersProgressBar(player, { chapters: this.chapters }) for (const chapter of this.chapters) {
const seekBar = this.getSeekBar() if (chapter.timecode === 0) continue
const playProgressBar = seekBar.getChild('playProgressBar')
if (playProgressBar) { const marker = new ProgressBarMarkerComponent(player, { timecode: chapter.timecode })
const playIndex = seekBar.children().indexOf(playProgressBar)
if (playIndex >= 0) { marker.on('mouseenter', () => {
seekBar.addChild(this.progressBar, {}, playIndex) this.activeChapter = chapter
} else { })
seekBar.addChild(this.progressBar)
} marker.on('mouseleave', () => {
} else { this.activeChapter = undefined
seekBar.addChild(this.progressBar) })
this.markers.push(marker)
this.getSeekBar().addChild(marker)
} }
}) })
} }
dispose () { dispose () {
if (this.progressBar) { for (const marker of this.markers) {
this.getSeekBar().removeChild(this.progressBar) this.getSeekBar().removeChild(marker)
this.progressBar = undefined
} }
super.dispose() super.dispose()
@@ -1,143 +0,0 @@
/**
* @file chapters-progress-bar.ts
*/
import { VideoChapter } from '@peertube/peertube-models'
import videojs from 'video.js'
import type { VideojsComponent, VideojsComponentOptions, VideojsPlayer } from '../../types'
import { sortBy } from '@peertube/peertube-core-utils'
const Component = videojs.getComponent('Component') as typeof VideojsComponent
type ChaptersProgressBarOptions = VideojsComponentOptions & {
chapters?: VideoChapter[]
segmentGap?: number
}
const clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max)
class ChaptersProgressBar extends Component {
declare private partEls_: HTMLElement[]
declare private chapters_: VideoChapter[]
declare private updateHandler_: () => void
declare options_: ChaptersProgressBarOptions
constructor (player: VideojsPlayer, options?: ChaptersProgressBarOptions) {
super(player, options)
this.partEls_ = []
this.chapters_ = options?.chapters || []
this.updateHandler_ = () => this.update()
this.player().on([ 'durationchange', 'loadedmetadata', 'resize', 'fullscreenchange' ], this.updateHandler_)
this.update()
}
createEl () {
return super.createEl('div', {
className: 'vjs-chapters-progress'
}, {
'aria-hidden': 'true'
})
}
setChapters (chapters: VideoChapter[]) {
this.chapters_ = chapters || []
this.update()
}
private clearSegments_ () {
const children = this.partEls_
for (let i = children.length - 1; i >= 0; i--) {
this.el_.removeChild(children[i])
}
this.partEls_.length = 0
}
update () {
const chapters = this.chapters_
const duration = this.player_.duration()
if (!chapters || chapters.length === 0 || !isFinite(duration) || duration <= 0) {
this.clearSegments_()
return
}
const seekBarEl = this.parentComponent_?.el()
const seekBarRect = seekBarEl ? videojs.dom.getBoundingClientRect(seekBarEl) : null
const gapPx = Math.max(0, this.options_.segmentGap || 0)
const gapPxValue = (seekBarRect && seekBarRect.width > 0) ? gapPx : 0
const children = this.partEls_
let partIndex = 0
const sortedChapters = sortBy(chapters.slice(), 'timecode')
if (sortedChapters[0].timecode > 0) {
sortedChapters.unshift({ timecode: 0, title: '' })
}
this.requestNamedAnimationFrame('ChaptersProgressBar#update', () => {
for (let i = 0; i < sortedChapters.length; i++) {
const chapter = sortedChapters[i]
const nextChapter = sortedChapters[i + 1]
const start = clamp(chapter.timecode, 0, duration)
const end = clamp(nextChapter?.timecode ?? duration, 0, duration)
if (end <= start) {
continue
}
const startPercent = (start / duration) * 100
const widthPercent = ((end - start) / duration) * 100
let part = children[partIndex]
if (!part) {
part = this.el_.appendChild(videojs.dom.createEl('div', {
className: 'vjs-chapters-progress-part'
})) as HTMLElement
children[partIndex] = part
}
const nextStart = start.toFixed(3)
const nextEnd = end.toFixed(3)
const nextGap = gapPxValue
if (part.dataset.start === nextStart && part.dataset.end === nextEnd && part.dataset.gap === String(nextGap)) {
partIndex++
continue
}
part.dataset.start = nextStart
part.dataset.end = nextEnd
part.dataset.gap = String(nextGap)
part.style.left = `${startPercent}%`
part.style.width = gapPxValue > 0
? `calc(${widthPercent}% - ${gapPxValue}px)`
: `${widthPercent}%`
partIndex++
}
for (let i = children.length - 1; i >= partIndex; i--) {
this.el_.removeChild(children[i])
}
children.length = partIndex
})
}
dispose () {
this.partEls_ = null
super.dispose()
}
}
ChaptersProgressBar.prototype.options_ = {
segmentGap: 2
}
Component.registerComponent('ChaptersProgressBar', ChaptersProgressBar as any)
export default ChaptersProgressBar
@@ -1,42 +0,0 @@
/**
* @file chapters-seek-bar.ts
*/
import videojs from 'video.js'
import type { VideojsComponent, VideojsComponentOptions, VideojsPlayer } from '../../types'
type SeekBarConstructor = typeof import('video.js/dist/types/control-bar/progress-control/seek-bar').default
type ChaptersSeekBarOptions = VideojsComponentOptions & {
children?: string[]
}
const Component = videojs.getComponent('Component') as typeof VideojsComponent
const SeekBar = videojs.getComponent('SeekBar') as SeekBarConstructor
const mergeOptions = videojs.mergeOptions as (...sources: any[]) => any
class ChaptersSeekBar extends SeekBar {
constructor (player: VideojsPlayer, options?: ChaptersSeekBarOptions) {
options = mergeOptions(ChaptersSeekBar.prototype.options_, options)
// Avoid mutating the prototype's `children` array by creating a copy
options.children = [ ...options.children ]
super(player, options as any)
}
createEl () {
const el = super.createEl()
return el
}
}
ChaptersSeekBar.prototype.options_ = mergeOptions(SeekBar.prototype.options_, {
children: [
'loadProgressBar',
'playProgressBar'
]
})
Component.registerComponent('ChaptersSeekBar', ChaptersSeekBar as any)
export default ChaptersSeekBar
@@ -0,0 +1,63 @@
import videojs from 'video.js'
import { ProgressBarMarkerComponentOptions, VideojsClickableComponent, VideojsClickableComponentOptions, VideojsPlayer } from '../../types'
const ClickableComponent = videojs.getComponent('ClickableComponent') as typeof VideojsClickableComponent
export class ProgressBarMarkerComponent extends ClickableComponent {
declare options_: ProgressBarMarkerComponentOptions & VideojsClickableComponentOptions
constructor (player: VideojsPlayer, options?: ProgressBarMarkerComponentOptions & VideojsClickableComponentOptions) {
super(player, options)
const updateMarker = () => {
if (!this.hasValidDuration()) return
const el = this.el() as HTMLElement
el.style.setProperty('left', this.buildLeftStyle())
el.style.setProperty('display', 'inline')
}
this.player().on('durationchange', updateMarker)
const stopPropagation = (event: Event) => event.stopPropagation()
this.on([ 'mousedown', 'touchstart' ], stopPropagation)
this.one('dispose', () => {
if (this.player()) this.player().off('durationchange', updateMarker)
if (this.el()) {
this.off([ 'mousedown', 'touchstart' ], stopPropagation)
}
})
}
createEl () {
return videojs.dom.createEl('span', {
className: 'vjs-chapter-marker',
style: this.hasValidDuration()
? `left: ${this.buildLeftStyle()}`
: 'display: none;'
}) as HTMLButtonElement
}
handleClick (event: Event) {
event.stopPropagation()
if (this.player()) this.player().currentTime(this.options_.timecode)
}
private buildLeftStyle () {
return `${(this.options_.timecode / this.player().duration()) * 100}%`
}
private hasValidDuration () {
const duration = this.player().duration()
if (isNaN(duration) || !duration) return false
return true
}
}
videojs.registerComponent('ProgressBarMarkerComponent', ProgressBarMarkerComponent)