mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-29 19:41:25 -08:00
feat: enable fullscreenWeb option in Artplayer configuration
feat: add danmuku plugin and i18n setup documentation - Introduced a new danmuku plugin with multiple run code options in the documentation. - Added language settings documentation for i18n, including default languages, import instructions, and examples for adding and modifying languages. - Implemented input handling for media sources in the artplayer-proxy-mediabunny package, supporting various formats including HLS. - Created m3u8 control functionalities for managing audio and video quality settings in the player. Update mediabunny.js
This commit is contained in:
1 parent
23c8f46878
commit
26841c5b1c
69 files changed
+49762
-28126
No files matched your search
@@ -3,12 +3,7 @@
|
||||
* Handles audio playback using Web Audio API
|
||||
*/
|
||||
import {
|
||||
ALL_FORMATS,
|
||||
AudioBufferSink,
|
||||
BlobSource,
|
||||
Input,
|
||||
ReadableStreamSource,
|
||||
UrlSource,
|
||||
} from 'mediabunny'
|
||||
|
||||
export default class AudioEngine {
|
||||
@@ -51,17 +46,6 @@ export default class AudioEngine {
|
||||
)
|
||||
}
|
||||
|
||||
normalizeSource(src) {
|
||||
if (typeof src === 'string')
|
||||
return new UrlSource(src)
|
||||
if (src instanceof Blob)
|
||||
return new BlobSource(src)
|
||||
if (typeof ReadableStream !== 'undefined' && src instanceof ReadableStream) {
|
||||
return new ReadableStreamSource(src)
|
||||
}
|
||||
return src
|
||||
}
|
||||
|
||||
ensureAudioContext(sampleRate) {
|
||||
if (this.audioContext)
|
||||
return
|
||||
@@ -97,8 +81,13 @@ export default class AudioEngine {
|
||||
this.audioIterator = null
|
||||
}
|
||||
|
||||
async load(src, onMetadata) {
|
||||
const id = ++this.asyncId
|
||||
handleNoAudioTrack() {
|
||||
this.audioSink = null
|
||||
this.ensureAudioContext()
|
||||
}
|
||||
|
||||
async load(media, onMetadata) {
|
||||
++this.asyncId
|
||||
|
||||
await this.stopIterator()
|
||||
this.stopQueuedNodes()
|
||||
@@ -107,23 +96,12 @@ export default class AudioEngine {
|
||||
this.playbackTimeAtStart = 0
|
||||
this.audioContextStartTime = 0
|
||||
|
||||
const source = this.normalizeSource(src)
|
||||
if (!source)
|
||||
return
|
||||
const { input, audioTrack, duration } = media
|
||||
this.input = input
|
||||
this.duration = duration
|
||||
|
||||
this.input = new Input({
|
||||
source,
|
||||
formats: ALL_FORMATS,
|
||||
})
|
||||
|
||||
this.duration = await this.input.computeDuration()
|
||||
if (id !== this.asyncId)
|
||||
return
|
||||
|
||||
const audioTrack = await this.input.getPrimaryAudioTrack()
|
||||
if (!audioTrack) {
|
||||
this.audioSink = null
|
||||
this.ensureAudioContext()
|
||||
this.handleNoAudioTrack()
|
||||
onMetadata?.()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Coordinates audio and video playback
|
||||
*/
|
||||
import AudioEngine from './AudioEngine.js'
|
||||
import { createInput, getHlsState, resolveDuration, selectPlaybackTracks } from './input.js'
|
||||
import VideoEngine from './VideoEngine.js'
|
||||
|
||||
export default class MediaBunnyEngine {
|
||||
@@ -31,6 +32,8 @@ export default class MediaBunnyEngine {
|
||||
this.error = null
|
||||
this.seeking = false
|
||||
this.loadSeq = 0
|
||||
this.input = null
|
||||
this.media = null
|
||||
|
||||
// Listen to ended event
|
||||
events.addEventListener?.('ended', () => {
|
||||
@@ -43,6 +46,7 @@ export default class MediaBunnyEngine {
|
||||
const id = ++this.loadSeq
|
||||
|
||||
this.pause()
|
||||
this.disposeInput()
|
||||
this.ended = false
|
||||
this.error = null
|
||||
this.networkState = 2 // NETWORK_LOADING
|
||||
@@ -73,8 +77,28 @@ export default class MediaBunnyEngine {
|
||||
}
|
||||
|
||||
async performLoad(src, id) {
|
||||
let videoMetadataLoaded = false
|
||||
let audioMetadataLoaded = false
|
||||
if (!(await this.video.preflight(src)))
|
||||
return
|
||||
|
||||
const input = createInput(src)
|
||||
if (!input) {
|
||||
this.video.handleNoVideoTrack()
|
||||
this.audio.handleNoAudioTrack()
|
||||
return
|
||||
}
|
||||
|
||||
const media = await selectPlaybackTracks(input, src)
|
||||
if (id !== this.loadSeq) {
|
||||
input.dispose()
|
||||
return
|
||||
}
|
||||
|
||||
this.disposeInput()
|
||||
this.input = input
|
||||
this.media = media
|
||||
|
||||
let videoMetadataLoaded = !media.videoTrack
|
||||
let audioMetadataLoaded = !media.audioTrack
|
||||
|
||||
const checkMetadata = () => {
|
||||
if (videoMetadataLoaded && audioMetadataLoaded) {
|
||||
@@ -87,13 +111,13 @@ export default class MediaBunnyEngine {
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
this.video.load(src, () => {
|
||||
this.video.load(media, () => {
|
||||
if (id !== this.loadSeq)
|
||||
return
|
||||
videoMetadataLoaded = true
|
||||
checkMetadata()
|
||||
}),
|
||||
this.audio.load(src, () => {
|
||||
this.audio.load(media, () => {
|
||||
if (id !== this.loadSeq)
|
||||
return
|
||||
audioMetadataLoaded = true
|
||||
@@ -128,6 +152,124 @@ export default class MediaBunnyEngine {
|
||||
})
|
||||
}
|
||||
|
||||
disposeInput() {
|
||||
this.input?.dispose()
|
||||
this.input = null
|
||||
this.media = null
|
||||
}
|
||||
|
||||
async replaceTracks({ videoTrack, audioTrack, videoMode, audioMode }) {
|
||||
if (!this.media)
|
||||
return
|
||||
|
||||
const nextMedia = {
|
||||
...this.media,
|
||||
videoTrack,
|
||||
audioTrack,
|
||||
videoMode,
|
||||
audioMode,
|
||||
}
|
||||
|
||||
const shouldResume = !this.paused
|
||||
const currentTime = this.currentTime
|
||||
|
||||
this.pause()
|
||||
this.ended = false
|
||||
this.seeking = true
|
||||
this.events.emit('seeking')
|
||||
this.events.emit('waiting')
|
||||
|
||||
nextMedia.duration = await resolveDuration(nextMedia)
|
||||
this.media = nextMedia
|
||||
|
||||
await Promise.all([
|
||||
this.video.load(nextMedia),
|
||||
this.audio.load(nextMedia),
|
||||
])
|
||||
|
||||
await Promise.all([
|
||||
this.video.seek(currentTime),
|
||||
this.audio.seek(currentTime),
|
||||
])
|
||||
|
||||
this.readyState = 4
|
||||
this.networkState = 1
|
||||
this.seeking = false
|
||||
this.events.emit('loadedmetadata')
|
||||
this.events.emit('durationchange')
|
||||
this.events.emit('progress')
|
||||
this.events.emit('loadeddata')
|
||||
this.events.emit('canplay')
|
||||
this.events.emit('canplaythrough')
|
||||
this.events.emit('seeked')
|
||||
|
||||
if (shouldResume) {
|
||||
await this.play()
|
||||
}
|
||||
}
|
||||
|
||||
async selectHlsQuality(value) {
|
||||
if (!this.media?.isHls || !this.input)
|
||||
return
|
||||
|
||||
const videoTracks = await this.input.getVideoTracks()
|
||||
const videoTrack = value === 'auto'
|
||||
? await this.input.getPrimaryVideoTrack()
|
||||
: videoTracks.find(track => track.id === value) ?? this.media.videoTrack
|
||||
|
||||
if (!videoTrack)
|
||||
return
|
||||
|
||||
let audioTrack = this.media.audioTrack
|
||||
let audioMode = this.media.audioMode
|
||||
|
||||
if (!audioTrack || !videoTrack.canBePairedWith(audioTrack)) {
|
||||
audioTrack = await videoTrack.getPrimaryPairableAudioTrack()
|
||||
audioMode = 'auto'
|
||||
}
|
||||
|
||||
await this.replaceTracks({
|
||||
videoTrack,
|
||||
audioTrack,
|
||||
videoMode: value === 'auto' ? 'auto' : 'manual',
|
||||
audioMode,
|
||||
})
|
||||
}
|
||||
|
||||
async selectHlsAudio(value) {
|
||||
if (!this.media?.isHls || !this.input)
|
||||
return
|
||||
|
||||
const audioTracks = await this.input.getAudioTracks()
|
||||
const audioTrack = value === 'auto'
|
||||
? this.media.videoTrack
|
||||
? await this.media.videoTrack.getPrimaryPairableAudioTrack()
|
||||
: await this.input.getPrimaryAudioTrack()
|
||||
: audioTracks.find(track => track.id === value) ?? this.media.audioTrack
|
||||
|
||||
if (!audioTrack)
|
||||
return
|
||||
|
||||
let videoTrack = this.media.videoTrack
|
||||
let videoMode = this.media.videoMode
|
||||
|
||||
if (!videoTrack || !audioTrack.canBePairedWith(videoTrack)) {
|
||||
videoTrack = await audioTrack.getPrimaryPairableVideoTrack()
|
||||
videoMode = 'auto'
|
||||
}
|
||||
|
||||
await this.replaceTracks({
|
||||
videoTrack,
|
||||
audioTrack,
|
||||
videoMode,
|
||||
audioMode: value === 'auto' ? 'auto' : 'manual',
|
||||
})
|
||||
}
|
||||
|
||||
getHlsState() {
|
||||
return getHlsState(this.media)
|
||||
}
|
||||
|
||||
async play() {
|
||||
if (!this.paused)
|
||||
return
|
||||
@@ -193,6 +335,7 @@ export default class MediaBunnyEngine {
|
||||
|
||||
destroy() {
|
||||
this.pause()
|
||||
this.disposeInput()
|
||||
this.audio.destroy()
|
||||
this.video.destroy()
|
||||
}
|
||||
@@ -203,7 +346,7 @@ export default class MediaBunnyEngine {
|
||||
}
|
||||
|
||||
get duration() {
|
||||
return this.audio.duration || this.video.duration
|
||||
return this.media?.duration ?? this.audio.duration ?? this.video.duration
|
||||
}
|
||||
|
||||
get videoWidth() {
|
||||
|
||||
@@ -3,13 +3,9 @@
|
||||
* Handles video frame rendering and synchronization
|
||||
*/
|
||||
import {
|
||||
ALL_FORMATS,
|
||||
BlobSource,
|
||||
CanvasSink,
|
||||
Input,
|
||||
ReadableStreamSource,
|
||||
UrlSource,
|
||||
} from 'mediabunny'
|
||||
import { isHlsSource } from './input.js'
|
||||
|
||||
export default class VideoEngine {
|
||||
constructor({
|
||||
@@ -55,19 +51,8 @@ export default class VideoEngine {
|
||||
this.isFetching = false
|
||||
}
|
||||
|
||||
normalizeSource(src) {
|
||||
if (typeof src === 'string')
|
||||
return new UrlSource(src)
|
||||
if (src instanceof Blob)
|
||||
return new BlobSource(src)
|
||||
if (typeof ReadableStream !== 'undefined' && src instanceof ReadableStream) {
|
||||
return new ReadableStreamSource(src)
|
||||
}
|
||||
return src
|
||||
}
|
||||
|
||||
async preflight(url) {
|
||||
if (!this.preflightRange || typeof url !== 'string')
|
||||
if (!this.preflightRange || typeof url !== 'string' || isHlsSource(url))
|
||||
return true
|
||||
|
||||
try {
|
||||
@@ -109,32 +94,17 @@ export default class VideoEngine {
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
||||
}
|
||||
|
||||
async load(src, onMetadata) {
|
||||
const id = ++this.asyncId
|
||||
async load(media, onMetadata) {
|
||||
++this.asyncId
|
||||
|
||||
await this.stopIterator()
|
||||
this.clear()
|
||||
this.posterDrawn = false
|
||||
|
||||
if (!(await this.preflight(src)))
|
||||
return
|
||||
const { input, videoTrack, duration } = media
|
||||
this.input = input
|
||||
this.duration = duration
|
||||
|
||||
const source = this.normalizeSource(src)
|
||||
if (!source) {
|
||||
this.drawPoster()
|
||||
return
|
||||
}
|
||||
|
||||
this.input = new Input({
|
||||
source,
|
||||
formats: ALL_FORMATS,
|
||||
})
|
||||
|
||||
this.duration = await this.input.computeDuration()
|
||||
if (id !== this.asyncId)
|
||||
return
|
||||
|
||||
const videoTrack = await this.input.getPrimaryVideoTrack()
|
||||
if (!videoTrack) {
|
||||
this.handleNoVideoTrack()
|
||||
onMetadata?.()
|
||||
@@ -190,7 +160,6 @@ export default class VideoEngine {
|
||||
|
||||
if (first) {
|
||||
this.ctx.drawImage(first.canvas, 0, 0)
|
||||
this.events.emit('loadeddata')
|
||||
}
|
||||
else {
|
||||
this.drawPoster()
|
||||
@@ -312,7 +281,7 @@ export default class VideoEngine {
|
||||
this.stop()
|
||||
this.stopIterator()
|
||||
this.posterDrawn = false
|
||||
this.input = null
|
||||
this.videoSink = null
|
||||
this.input = null
|
||||
}
|
||||
}
|
||||
@@ -194,6 +194,18 @@ export default class VideoShim {
|
||||
this.engine.load(this._src)
|
||||
}
|
||||
|
||||
getM3u8State() {
|
||||
return this.engine.getHlsState()
|
||||
}
|
||||
|
||||
switchM3u8Quality(value) {
|
||||
return this.engine.selectHlsQuality(value)
|
||||
}
|
||||
|
||||
switchM3u8Audio(value) {
|
||||
return this.engine.selectHlsAudio(value)
|
||||
}
|
||||
|
||||
// Video dimensions
|
||||
get videoWidth() {
|
||||
return this.engine.videoWidth
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { setupM3u8Controls } from './m3u8.js'
|
||||
/**
|
||||
* ArtPlayer MediaBunny Proxy
|
||||
* Main entry point
|
||||
@@ -20,6 +21,7 @@ export default function artplayerProxyMediabunny(option = {}) {
|
||||
ctx,
|
||||
option,
|
||||
})
|
||||
art.mediabunny = shim
|
||||
|
||||
// Proxy canvas methods to shim
|
||||
const originalCanvasMethods = {}
|
||||
@@ -74,9 +76,11 @@ export default function artplayerProxyMediabunny(option = {}) {
|
||||
|
||||
art.on('resize', resize)
|
||||
art.on('video:loadedmetadata', resize)
|
||||
setupM3u8Controls({ art, shim, option })
|
||||
|
||||
// Cleanup on destroy
|
||||
art.on('destroy', () => {
|
||||
delete art.mediabunny
|
||||
shim.destroy()
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
import {
|
||||
ALL_FORMATS,
|
||||
BlobSource,
|
||||
HLS_FORMATS,
|
||||
Input,
|
||||
ReadableStreamSource,
|
||||
UrlSource,
|
||||
} from 'mediabunny'
|
||||
|
||||
const M3U8_RE = /\.m3u8(?:$|[?#])/i
|
||||
|
||||
export function isHlsSource(src) {
|
||||
return typeof src === 'string' && M3U8_RE.test(src)
|
||||
}
|
||||
|
||||
export function normalizeSource(src) {
|
||||
if (typeof src === 'string')
|
||||
return new UrlSource(src)
|
||||
if (src instanceof Blob)
|
||||
return new BlobSource(src)
|
||||
if (typeof ReadableStream !== 'undefined' && src instanceof ReadableStream) {
|
||||
return new ReadableStreamSource(src)
|
||||
}
|
||||
return src ?? null
|
||||
}
|
||||
|
||||
export function createInput(src) {
|
||||
const source = normalizeSource(src)
|
||||
if (!source)
|
||||
return null
|
||||
|
||||
return new Input({
|
||||
source,
|
||||
formats: isHlsSource(src) ? HLS_FORMATS : ALL_FORMATS,
|
||||
})
|
||||
}
|
||||
|
||||
export async function resolveDuration({ input, videoTrack, audioTrack }) {
|
||||
const tracks = [videoTrack, audioTrack].filter(Boolean)
|
||||
const referenceTrack = videoTrack || audioTrack
|
||||
const isLive = Boolean(referenceTrack && await referenceTrack.isLive())
|
||||
|
||||
let duration = Number.NaN
|
||||
if (tracks.length > 0) {
|
||||
duration = await input.getDurationFromMetadata(tracks, { skipLiveWait: true })
|
||||
if (duration === null && !isLive) {
|
||||
duration = await input.computeDuration(tracks, { skipLiveWait: true })
|
||||
}
|
||||
}
|
||||
|
||||
return isLive ? Number.POSITIVE_INFINITY : duration ?? Number.NaN
|
||||
}
|
||||
|
||||
export async function selectPlaybackTracks(input, src) {
|
||||
const videoTrack = await input.getPrimaryVideoTrack()
|
||||
const audioTrack = videoTrack
|
||||
? await videoTrack.getPrimaryPairableAudioTrack()
|
||||
: await input.getPrimaryAudioTrack()
|
||||
const duration = await resolveDuration({ input, videoTrack, audioTrack })
|
||||
|
||||
return {
|
||||
input,
|
||||
videoTrack,
|
||||
audioTrack,
|
||||
duration,
|
||||
isLive: Number.isFinite(duration) ? false : Boolean((videoTrack || audioTrack) && await (videoTrack || audioTrack).isLive()),
|
||||
isHls: isHlsSource(src),
|
||||
videoMode: isHlsSource(src) ? 'auto' : 'manual',
|
||||
audioMode: isHlsSource(src) ? 'auto' : 'manual',
|
||||
}
|
||||
}
|
||||
|
||||
async function getTrackBitrate(track) {
|
||||
return await track.getAverageBitrate() ?? await track.getBitrate() ?? 0
|
||||
}
|
||||
|
||||
export async function getHlsState(media) {
|
||||
if (!media?.isHls || !media.input)
|
||||
return null
|
||||
|
||||
const [videoTracks, pairableAudioTracks] = await Promise.all([
|
||||
media.input.getVideoTracks(),
|
||||
media.videoTrack
|
||||
? media.videoTrack.getPairableAudioTracks()
|
||||
: [],
|
||||
])
|
||||
|
||||
const [levels, audios] = await Promise.all([
|
||||
Promise.all(videoTracks.map(async (track, index) => ({
|
||||
id: track.id,
|
||||
track,
|
||||
index,
|
||||
name: await track.getName(),
|
||||
height: await track.getDisplayHeight(),
|
||||
bitrate: await getTrackBitrate(track),
|
||||
}))),
|
||||
Promise.all(pairableAudioTracks.map(async (track, index) => {
|
||||
const name = await track.getName()
|
||||
const language = await track.getLanguageCode()
|
||||
return {
|
||||
id: track.id,
|
||||
track,
|
||||
index,
|
||||
name,
|
||||
lang: language,
|
||||
language,
|
||||
bitrate: await getTrackBitrate(track),
|
||||
}
|
||||
})),
|
||||
])
|
||||
|
||||
return {
|
||||
levels,
|
||||
audios,
|
||||
currentLevel: media.videoTrack ? levels.find(item => item.id === media.videoTrack.id) ?? null : null,
|
||||
currentAudio: media.audioTrack ? audios.find(item => item.id === media.audioTrack.id) ?? null : null,
|
||||
videoMode: media.videoMode ?? 'auto',
|
||||
audioMode: media.audioMode ?? 'auto',
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
import $audio from '../../artplayer-plugin-hls-control/src/audio.svg?raw'
|
||||
import $quality from '../../artplayer-plugin-hls-control/src/quality.svg?raw'
|
||||
|
||||
function uniqBy(array, property) {
|
||||
const seen = new Map()
|
||||
return array.filter((item) => {
|
||||
const key = item[property]
|
||||
if (key === undefined) {
|
||||
return true
|
||||
}
|
||||
return !seen.has(key) && seen.set(key, 1)
|
||||
})
|
||||
}
|
||||
|
||||
export function setupM3u8Controls({ art, shim, option }) {
|
||||
if (!option.m3u8)
|
||||
return
|
||||
|
||||
function removeControl(name) {
|
||||
if (art.controls.cache.has(name)) {
|
||||
art.controls.remove(name)
|
||||
}
|
||||
}
|
||||
|
||||
function removeSetting(name) {
|
||||
if (art.setting.find(name)) {
|
||||
art.setting.remove(name)
|
||||
}
|
||||
}
|
||||
|
||||
function clearQuality() {
|
||||
removeControl('mediabunny-quality')
|
||||
removeSetting('mediabunny-quality')
|
||||
}
|
||||
|
||||
function clearAudio() {
|
||||
removeControl('mediabunny-audio')
|
||||
removeSetting('mediabunny-audio')
|
||||
}
|
||||
|
||||
function canRender(config = {}) {
|
||||
return config.control || config.setting
|
||||
}
|
||||
|
||||
async function updateQuality(state) {
|
||||
const config = option.m3u8?.quality || {}
|
||||
if (!canRender(config) || !state?.levels.length) {
|
||||
clearQuality()
|
||||
return
|
||||
}
|
||||
|
||||
const auto = config.auto || 'Auto'
|
||||
const title = config.title || 'Quality'
|
||||
const getName = config.getName || (level => level.name || `${level.height}P`)
|
||||
const defaultHtml = state.currentLevel
|
||||
? getName(state.currentLevel)
|
||||
: auto
|
||||
|
||||
const selector = uniqBy(
|
||||
state.levels.map((item) => {
|
||||
return {
|
||||
html: getName(item),
|
||||
value: item.id,
|
||||
default: state.currentLevel?.id === item.id,
|
||||
}
|
||||
}),
|
||||
'html',
|
||||
).sort((a, b) => {
|
||||
const left = state.levels.find(item => item.id === a.value)
|
||||
const right = state.levels.find(item => item.id === b.value)
|
||||
return (right?.height || 0) - (left?.height || 0)
|
||||
})
|
||||
|
||||
selector.push({
|
||||
html: auto,
|
||||
value: 'auto',
|
||||
default: !state.currentLevel,
|
||||
})
|
||||
|
||||
const onSelect = async (item) => {
|
||||
await shim.switchM3u8Quality(item.value)
|
||||
art.notice.show = `${title}: ${item.html}`
|
||||
await update()
|
||||
return item.html
|
||||
}
|
||||
|
||||
if (config.control) {
|
||||
art.controls.update({
|
||||
name: 'mediabunny-quality',
|
||||
position: 'right',
|
||||
html: defaultHtml,
|
||||
style: { padding: '0 10px' },
|
||||
selector,
|
||||
onSelect,
|
||||
})
|
||||
}
|
||||
|
||||
if (config.setting) {
|
||||
art.setting.update({
|
||||
name: 'mediabunny-quality',
|
||||
tooltip: defaultHtml,
|
||||
html: title,
|
||||
icon: $quality,
|
||||
width: 200,
|
||||
selector,
|
||||
onSelect,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function updateAudio(state) {
|
||||
const config = option.m3u8?.audio || {}
|
||||
if (!canRender(config) || !state?.audios.length || state.audios.length < 2) {
|
||||
clearAudio()
|
||||
return
|
||||
}
|
||||
|
||||
const auto = config.auto || 'Auto'
|
||||
const title = config.title || 'Audio'
|
||||
const getName = config.getName || (track => track.name || track.lang || track.language)
|
||||
const defaultHtml = state.currentAudio
|
||||
? getName(state.currentAudio)
|
||||
: auto
|
||||
|
||||
const selector = uniqBy(
|
||||
state.audios.map((item) => {
|
||||
return {
|
||||
html: getName(item),
|
||||
value: item.id,
|
||||
default: state.currentAudio?.id === item.id,
|
||||
}
|
||||
}),
|
||||
'html',
|
||||
)
|
||||
|
||||
selector.push({
|
||||
html: auto,
|
||||
value: 'auto',
|
||||
default: !state.currentAudio,
|
||||
})
|
||||
|
||||
const onSelect = async (item) => {
|
||||
await shim.switchM3u8Audio(item.value)
|
||||
art.notice.show = `${title}: ${item.html}`
|
||||
await update()
|
||||
return item.html
|
||||
}
|
||||
|
||||
if (config.control) {
|
||||
art.controls.update({
|
||||
name: 'mediabunny-audio',
|
||||
position: 'right',
|
||||
html: defaultHtml,
|
||||
style: { padding: '0 10px' },
|
||||
selector,
|
||||
onSelect,
|
||||
})
|
||||
}
|
||||
|
||||
if (config.setting) {
|
||||
art.setting.update({
|
||||
name: 'mediabunny-audio',
|
||||
tooltip: defaultHtml,
|
||||
html: title,
|
||||
icon: $audio,
|
||||
width: 200,
|
||||
selector,
|
||||
onSelect,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function update() {
|
||||
const state = await shim.getM3u8State()
|
||||
if (!state) {
|
||||
clearQuality()
|
||||
clearAudio()
|
||||
return
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
updateQuality(state),
|
||||
updateAudio(state),
|
||||
])
|
||||
}
|
||||
|
||||
art.on('video:loadedmetadata', update)
|
||||
art.on('restart', update)
|
||||
|
||||
return { update }
|
||||
}
|
||||
Reference in new issue
Block a user