98 lines
3.3 KiB
TypeScript
98 lines
3.3 KiB
TypeScript
import { TimelineBounds } from './types'
|
|
import { state } from './state'
|
|
|
|
const formatTime = (seconds: number): string => {
|
|
if (!Number.isFinite(seconds) || seconds < 0) return '00:00'
|
|
const mins = Math.floor(seconds / 60)
|
|
const secs = Math.floor(seconds % 60)
|
|
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
|
|
}
|
|
|
|
let cachedBounds: TimelineBounds = { start: 0, end: 0, duration: 0 }
|
|
|
|
export const timelineBounds = (): TimelineBounds => {
|
|
const duration = Number.isFinite(state.video.duration) && state.video.duration > 0 ? state.video.duration : 0
|
|
let start = 0
|
|
if (state.video.seekable.length > 0) {
|
|
const seekableStart = state.video.seekable.start(0)
|
|
if (Number.isFinite(seekableStart) && seekableStart > 0) start = seekableStart
|
|
}
|
|
if (duration > start) {
|
|
return { start, end: duration, duration: duration - start }
|
|
}
|
|
if (state.video.seekable.length > 0) {
|
|
const seekableEnd = state.video.seekable.end(state.video.seekable.length - 1)
|
|
if (Number.isFinite(seekableEnd) && seekableEnd > start) {
|
|
return { start, end: seekableEnd, duration: seekableEnd - start }
|
|
}
|
|
}
|
|
return { start: 0, end: duration, duration }
|
|
}
|
|
|
|
export const invalidateBounds = (): void => {
|
|
cachedBounds = timelineBounds()
|
|
}
|
|
|
|
export const getBounds = (): TimelineBounds => cachedBounds
|
|
|
|
export const displayTimeFromAbsolute = (absoluteTime: number): number => {
|
|
const b = getBounds()
|
|
if (!Number.isFinite(absoluteTime) || b.duration <= 0) return 0
|
|
return Math.max(b.start, Math.min(b.end, absoluteTime)) - b.start
|
|
}
|
|
|
|
export const absoluteTimeFromDisplay = (displayTime: number): number => {
|
|
const b = getBounds()
|
|
if (!Number.isFinite(displayTime) || b.duration <= 0) return 0
|
|
return b.start + Math.max(0, Math.min(b.duration, displayTime))
|
|
}
|
|
|
|
export const absoluteTimeFromRatio = (ratio: number): number => {
|
|
const b = getBounds()
|
|
if (!Number.isFinite(ratio) || b.duration <= 0) return 0
|
|
return b.start + Math.max(0, Math.min(1, ratio)) * b.duration
|
|
}
|
|
|
|
export const getBufferedEnd = (): number => {
|
|
const currentTime = state.video.currentTime
|
|
let end = 0
|
|
for (let i = 0; i < state.video.buffered.length; i++) {
|
|
if (state.video.buffered.start(i) <= currentTime && state.video.buffered.end(i) >= currentTime) {
|
|
end = state.video.buffered.end(i)
|
|
break
|
|
}
|
|
}
|
|
if (end === 0) {
|
|
for (let i = 0; i < state.video.buffered.length; i++) {
|
|
if (state.video.buffered.end(i) > currentTime) {
|
|
end = Math.max(end, state.video.buffered.end(i))
|
|
}
|
|
}
|
|
}
|
|
return end
|
|
}
|
|
|
|
export const updateTimeline = (currentTime: number): void => {
|
|
const { progress, scrubber, timeDisplay, durationDisplay, buffered } = getTimelineEls()
|
|
const b = getBounds()
|
|
|
|
if (b.duration <= 0) {
|
|
progress.style.width = '0%'
|
|
buffered.style.width = '0%'
|
|
scrubber.style.left = '0%'
|
|
timeDisplay.textContent = '00:00'
|
|
durationDisplay.textContent = '00:00'
|
|
return
|
|
}
|
|
|
|
const pct = (displayTimeFromAbsolute(currentTime) / b.duration) * 100
|
|
progress.style.width = `${pct}%`
|
|
scrubber.style.left = `${pct}%`
|
|
timeDisplay.textContent = formatTime(displayTimeFromAbsolute(currentTime))
|
|
durationDisplay.textContent = formatTime(b.duration)
|
|
|
|
const bufferedEnd = getBufferedEnd()
|
|
const bufferedPct = (displayTimeFromAbsolute(bufferedEnd) / b.duration) * 100
|
|
buffered.style.width = `${bufferedPct}%`
|
|
}
|