mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-24 23:51:20 -08:00
refactor(core): CORE-10 type playback properties and add compatible controls view
This commit is contained in:
1 parent
44f4e083a9
commit
5e31274f1d
48 files changed
+3846
-248
No files matched your search
@@ -0,0 +1,38 @@
|
||||
export interface Position {
|
||||
readonly duration: number
|
||||
get currentTime(): number
|
||||
set currentTime(time: number | string)
|
||||
}
|
||||
|
||||
export interface SeekMethods {
|
||||
get seek(): undefined
|
||||
set seek(time: number | string)
|
||||
get forward(): undefined
|
||||
set forward(time: number)
|
||||
get backward(): undefined
|
||||
set backward(time: number)
|
||||
}
|
||||
|
||||
export interface SeekHost extends Position {
|
||||
notice: { set show(message: string) }
|
||||
emit: (name: 'seek', currentTime: number, requestedTime: number | string) => unknown
|
||||
}
|
||||
|
||||
export interface PlaybackNotice {
|
||||
i18n: { get: (key: string) => string }
|
||||
notice: { set show(message: string) }
|
||||
}
|
||||
|
||||
export interface VolumeHost extends PlaybackNotice {
|
||||
template: { $video: Pick<HTMLMediaElement, 'volume' | 'muted'> }
|
||||
storage: { set: (key: 'volume', value: number) => unknown }
|
||||
emit: (name: 'muted', value: boolean) => unknown
|
||||
}
|
||||
|
||||
export type DisplayState = 'mini' | 'pip' | 'fullscreen' | 'fullscreenWeb'
|
||||
export type DisplayStates = Record<DisplayState, boolean>
|
||||
|
||||
export interface StateProperty {
|
||||
get state(): DisplayState | 'standard'
|
||||
set state(name: string)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { clamp, def } from '../utils'
|
||||
|
||||
export default function currentTimeMix(art) {
|
||||
const { $video } = art.template
|
||||
|
||||
def(art, 'currentTime', {
|
||||
get: () => $video.currentTime || 0,
|
||||
set: (time) => {
|
||||
time = Number.parseFloat(time)
|
||||
if (Number.isNaN(time))
|
||||
return
|
||||
$video.currentTime = clamp(time, 0, art.duration)
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { MediaHost } from '../media/hosts'
|
||||
import type { Position } from '../media/playback'
|
||||
import { clamp, def } from '../utils'
|
||||
|
||||
export default function currentTimeMix(art: MediaHost<Pick<HTMLMediaElement, 'currentTime'>> & Pick<Position, 'duration'>): asserts art is typeof art & Position {
|
||||
const { $video } = art.template
|
||||
def(art, 'currentTime', {
|
||||
get: () => $video.currentTime || 0,
|
||||
set: (time: number | string) => {
|
||||
// parseFloat performs the historical JS coercion; a cast must not change it.
|
||||
const parsed = Number.parseFloat(time as string)
|
||||
if (Number.isNaN(parsed))
|
||||
return
|
||||
$video.currentTime = clamp(parsed, 0, art.duration)
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { def } from '../utils'
|
||||
|
||||
export default function loadedMix(art) {
|
||||
const { $video } = art.template
|
||||
|
||||
def(art, 'loaded', {
|
||||
get: () => art.loadedTime / $video.duration,
|
||||
})
|
||||
|
||||
def(art, 'loadedTime', {
|
||||
get: () => {
|
||||
return $video.buffered.length ? $video.buffered.end($video.buffered.length - 1) : 0
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { MediaHost } from '../media/hosts'
|
||||
import { def } from '../utils'
|
||||
|
||||
type LoadedHost = MediaHost<Pick<HTMLMediaElement, 'duration' | 'buffered'>>
|
||||
interface LoadedState {
|
||||
readonly loaded: number
|
||||
readonly loadedTime: number
|
||||
}
|
||||
|
||||
export default function loadedMix(art: LoadedHost): asserts art is LoadedHost & LoadedState {
|
||||
const { $video } = art.template
|
||||
const target = art as LoadedHost & LoadedState
|
||||
def(art, 'loaded', { get: () => target.loadedTime / $video.duration })
|
||||
def(art, 'loadedTime', {
|
||||
get: () => $video.buffered.length ? $video.buffered.end($video.buffered.length - 1) : 0,
|
||||
})
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import { def } from '../utils'
|
||||
|
||||
export default function playbackRateMix(art) {
|
||||
const {
|
||||
template: { $video },
|
||||
i18n,
|
||||
notice,
|
||||
} = art
|
||||
|
||||
def(art, 'playbackRate', {
|
||||
get() {
|
||||
return $video.playbackRate
|
||||
},
|
||||
set(rate) {
|
||||
if (rate) {
|
||||
if (rate === $video.playbackRate)
|
||||
return
|
||||
$video.playbackRate = rate
|
||||
notice.show = `${i18n.get('Rate')}: ${rate === 1.0 ? i18n.get('Normal') : `${rate}x`}`
|
||||
}
|
||||
else {
|
||||
art.playbackRate = 1
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { MediaHost } from '../media/hosts'
|
||||
import type { PlaybackNotice } from '../media/playback'
|
||||
import { def } from '../utils'
|
||||
|
||||
type RateHost = MediaHost<Pick<HTMLMediaElement, 'playbackRate'>> & PlaybackNotice
|
||||
|
||||
export default function playbackRateMix(art: RateHost): asserts art is RateHost & { playbackRate: number } {
|
||||
const { template: { $video }, i18n, notice } = art
|
||||
const target = art as RateHost & { playbackRate: number }
|
||||
def(art, 'playbackRate', {
|
||||
get() {
|
||||
return $video.playbackRate
|
||||
},
|
||||
set(rate: number) {
|
||||
if (rate) {
|
||||
if (rate === $video.playbackRate)
|
||||
return
|
||||
$video.playbackRate = rate
|
||||
notice.show = `${i18n.get('Rate')}: ${rate === 1.0 ? i18n.get('Normal') : `${rate}x`}`
|
||||
}
|
||||
else {
|
||||
target.playbackRate = 1
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { def } from '../utils'
|
||||
|
||||
export default function playedMix(art) {
|
||||
def(art, 'played', {
|
||||
get: () => art.currentTime / art.duration,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { Position } from '../media/playback'
|
||||
import { def } from '../utils'
|
||||
|
||||
export default function playedMix(art: Position): asserts art is Position & { readonly played: number } {
|
||||
def(art, 'played', { get: () => art.currentTime / art.duration })
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { def, secondToTime } from '../utils'
|
||||
|
||||
export default function seekMix(art) {
|
||||
const { notice } = art
|
||||
|
||||
def(art, 'seek', {
|
||||
set(time) {
|
||||
art.currentTime = time
|
||||
if (art.duration) {
|
||||
notice.show = `${secondToTime(art.currentTime)} / ${secondToTime(art.duration)}`
|
||||
}
|
||||
art.emit('seek', art.currentTime, time)
|
||||
},
|
||||
})
|
||||
|
||||
def(art, 'forward', {
|
||||
set(time) {
|
||||
art.seek = art.currentTime + time
|
||||
},
|
||||
})
|
||||
|
||||
def(art, 'backward', {
|
||||
set(time) {
|
||||
art.seek = art.currentTime - time
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { SeekHost, SeekMethods } from '../media/playback'
|
||||
import { def, secondToTime } from '../utils'
|
||||
|
||||
export default function seekMix(art: SeekHost): asserts art is SeekHost & SeekMethods {
|
||||
const { notice } = art
|
||||
def(art, 'seek', {
|
||||
set(time: number | string) {
|
||||
art.currentTime = time
|
||||
if (art.duration)
|
||||
notice.show = `${secondToTime(art.currentTime)} / ${secondToTime(art.duration)}`
|
||||
art.emit('seek', art.currentTime, time)
|
||||
},
|
||||
})
|
||||
// The seek descriptor is installed above; preserve forwarding through it.
|
||||
const target = art as SeekHost & SeekMethods
|
||||
def(art, 'forward', {
|
||||
set(time: number) {
|
||||
target.seek = art.currentTime + time
|
||||
},
|
||||
})
|
||||
def(art, 'backward', {
|
||||
set(time: number) {
|
||||
target.seek = art.currentTime - time
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { def } from '../utils'
|
||||
|
||||
export default function stateMix(art) {
|
||||
const states = ['mini', 'pip', 'fullscreen', 'fullscreenWeb']
|
||||
def(art, 'state', {
|
||||
get: () => states.find(name => art[name]) || 'standard',
|
||||
set(name) {
|
||||
for (let index = 0; index < states.length; index++) {
|
||||
const prop = states[index]
|
||||
if (prop !== name && art[prop]) {
|
||||
art[prop] = false
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { DisplayStates, StateProperty } from '../media/playback'
|
||||
import { def } from '../utils'
|
||||
|
||||
export default function stateMix(art: DisplayStates): asserts art is DisplayStates & StateProperty {
|
||||
const states = ['mini', 'pip', 'fullscreen', 'fullscreenWeb'] as const
|
||||
def(art, 'state', {
|
||||
get: () => states.find(name => art[name]) || 'standard',
|
||||
set(name: string) {
|
||||
for (let index = 0; index < states.length; index++) {
|
||||
const prop = states[index]!
|
||||
if (prop !== name && art[prop])
|
||||
art[prop] = false
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { def } from '../utils'
|
||||
|
||||
export default function toggleMix(art) {
|
||||
def(art, 'toggle', {
|
||||
value() {
|
||||
if (art.playing) {
|
||||
return art.pause()
|
||||
}
|
||||
else {
|
||||
return art.play()
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { PlaybackMethods } from '../media/types'
|
||||
import { def } from '../utils'
|
||||
|
||||
export default function toggleMix<PlayResult, PauseResult>(art: PlaybackMethods<PlayResult, PauseResult> & { readonly playing: boolean }): asserts art is typeof art & { toggle: () => PlayResult | PauseResult } {
|
||||
def(art, 'toggle', {
|
||||
value() {
|
||||
if (art.playing)
|
||||
return art.pause()
|
||||
else
|
||||
return art.play()
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { clamp, def } from '../utils'
|
||||
|
||||
export default function volumeMix(art) {
|
||||
const {
|
||||
template: { $video },
|
||||
i18n,
|
||||
notice,
|
||||
storage,
|
||||
} = art
|
||||
|
||||
def(art, 'volume', {
|
||||
get: () => $video.volume || 0,
|
||||
set: (percentage) => {
|
||||
$video.volume = clamp(percentage, 0, 1)
|
||||
notice.show = `${i18n.get('Volume')}: ${Number.parseInt($video.volume * 100, 10)}`
|
||||
if ($video.volume !== 0) {
|
||||
storage.set('volume', $video.volume)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
def(art, 'muted', {
|
||||
get: () => $video.muted,
|
||||
set: (muted) => {
|
||||
$video.muted = muted
|
||||
art.emit('muted', muted)
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { VolumeHost } from '../media/playback'
|
||||
import { clamp, def } from '../utils'
|
||||
|
||||
export default function volumeMix(art: VolumeHost): asserts art is VolumeHost & Pick<HTMLMediaElement, 'volume' | 'muted'> {
|
||||
const { template: { $video }, i18n, notice, storage } = art
|
||||
def(art, 'volume', {
|
||||
get: () => $video.volume || 0,
|
||||
set: (percentage: number) => {
|
||||
$video.volume = clamp(percentage, 0, 1)
|
||||
notice.show = `${i18n.get('Volume')}: ${Number.parseInt(String($video.volume * 100), 10)}`
|
||||
if ($video.volume !== 0)
|
||||
storage.set('volume', $video.volume)
|
||||
},
|
||||
})
|
||||
def(art, 'muted', {
|
||||
get: () => $video.muted,
|
||||
set: (muted: boolean) => {
|
||||
$video.muted = muted
|
||||
art.emit('muted', muted)
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -40,5 +40,6 @@ export interface SwitchHost extends UrlTarget, SourceEvents {
|
||||
export interface SwitchMethods {
|
||||
switchUrl: (url: string) => Promise<void>
|
||||
switchQuality: (url: string) => Promise<void>
|
||||
get switch(): undefined
|
||||
set switch(url: string)
|
||||
}
|
||||
Reference in new issue
Block a user