714 lines
20 KiB
JavaScript
714 lines
20 KiB
JavaScript
const audioPages = require('../../data/audioPages')
|
|
|
|
const SEEK_TIMEOUT_MS = 1600
|
|
const PAUSE_LOCK_TIMEOUT_MS = 1200
|
|
const RATE_VALUES = Object.freeze([0.8, 1, 1.2])
|
|
const RATE_OPTIONS = Object.freeze([
|
|
Object.freeze({ value: 0.8, label: '0.8倍' }),
|
|
Object.freeze({ value: 1, label: '1.0倍' }),
|
|
Object.freeze({ value: 1.2, label: '1.2倍' }),
|
|
])
|
|
const FALLBACK_RATE_OPTIONS = Object.freeze([
|
|
Object.freeze({ value: 1, label: '1.0倍' }),
|
|
])
|
|
|
|
function formatTime(value) {
|
|
const seconds = Math.max(0, Math.floor(Number(value) || 0))
|
|
return `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, '0')}`
|
|
}
|
|
|
|
function clamp(value, minimum, maximum) {
|
|
return Math.min(maximum, Math.max(minimum, Number(value) || 0))
|
|
}
|
|
|
|
function eventValue(event) {
|
|
return Number(event && event.detail && event.detail.value) || 0
|
|
}
|
|
|
|
Page({
|
|
data: {
|
|
pageId: '',
|
|
pageNumber: 1,
|
|
title: '',
|
|
caption: '',
|
|
imageSrc: '',
|
|
playing: false,
|
|
loading: false,
|
|
audioReady: false,
|
|
hasStarted: false,
|
|
seekLocked: false,
|
|
error: '',
|
|
currentLabel: '0:00',
|
|
durationLabel: '0:00',
|
|
durationSeconds: 1,
|
|
sliderValue: 0,
|
|
progressStyle: 'width:0%',
|
|
playbackRate: 1,
|
|
rateOptions: FALLBACK_RATE_OPTIONS,
|
|
rateControlsVisible: false,
|
|
reviewStatus: 'technical-qa-pass-human-listening-pending',
|
|
},
|
|
|
|
onLoad(options = {}) {
|
|
this._unloaded = false
|
|
this._lifecycleGeneration = (Number(this._lifecycleGeneration) || 0) + 1
|
|
this._contextGeneration = 0
|
|
this._intentGeneration = 0
|
|
this._seekOperationGeneration = 0
|
|
this._controlOperationGeneration = 0
|
|
this._desiredPlayback = false
|
|
this._activePlayIntentGeneration = 0
|
|
this._controlLocked = false
|
|
this._scrubbing = false
|
|
this._pendingSeek = null
|
|
this._seekTimer = null
|
|
this._pendingPauseLock = null
|
|
this._pauseLockTimer = null
|
|
this._page = null
|
|
this.bindAudioInterruptionHandlers()
|
|
const pageId = String(options.pageId || '')
|
|
const page = audioPages[pageId]
|
|
if (!page) {
|
|
this.setData({ error: '这一页的声音没有找到,请返回连环画。' })
|
|
return
|
|
}
|
|
this._page = page
|
|
const durationSeconds = Math.max(1, Number(page.durationSeconds) || 1)
|
|
this.setData({
|
|
pageId,
|
|
pageNumber: page.pageNumber,
|
|
title: page.title,
|
|
caption: page.caption,
|
|
imageSrc: page.imageSrc,
|
|
durationLabel: formatTime(durationSeconds),
|
|
durationSeconds,
|
|
})
|
|
},
|
|
|
|
onReady() {
|
|
if (!this._page || this._unloaded) return
|
|
this.createAudioContext(this._page.audioSrc)
|
|
},
|
|
|
|
isCurrentContext(context, generation) {
|
|
return !this._unloaded
|
|
&& this.audioContext === context
|
|
&& this._contextGeneration === generation
|
|
},
|
|
|
|
setProgress(value, durationValue) {
|
|
const duration = Math.max(
|
|
1,
|
|
Number(durationValue) || Number(this.data.durationSeconds) || 1,
|
|
)
|
|
const current = clamp(value, 0, duration)
|
|
const percent = Math.min(100, current / duration * 100)
|
|
this.setData({
|
|
currentLabel: formatTime(current),
|
|
durationLabel: formatTime(duration),
|
|
durationSeconds: duration,
|
|
sliderValue: current,
|
|
progressStyle: `width:${percent}%`,
|
|
})
|
|
},
|
|
|
|
disableRateControls(context) {
|
|
try {
|
|
if (context && typeof context.playbackRate === 'number') {
|
|
context.playbackRate = 1
|
|
}
|
|
} catch (_) {
|
|
// A runtime that rejects playbackRate remains safely at normal speed.
|
|
}
|
|
this.setData({
|
|
playbackRate: 1,
|
|
rateOptions: FALLBACK_RATE_OPTIONS,
|
|
rateControlsVisible: false,
|
|
})
|
|
},
|
|
|
|
configureRateControls(context, generation) {
|
|
try {
|
|
if (typeof context.playbackRate !== 'number') {
|
|
this.disableRateControls(context)
|
|
return
|
|
}
|
|
for (const rate of RATE_VALUES) {
|
|
context.playbackRate = rate
|
|
if (Math.abs(Number(context.playbackRate) - rate) > 0.001) {
|
|
this.disableRateControls(context)
|
|
return
|
|
}
|
|
}
|
|
context.playbackRate = 1
|
|
if (
|
|
!this.isCurrentContext(context, generation)
|
|
|| Math.abs(Number(context.playbackRate) - 1) > 0.001
|
|
) {
|
|
this.disableRateControls(context)
|
|
return
|
|
}
|
|
this.setData({
|
|
playbackRate: 1,
|
|
rateOptions: RATE_OPTIONS,
|
|
rateControlsVisible: true,
|
|
})
|
|
} catch (_) {
|
|
this.disableRateControls(context)
|
|
}
|
|
},
|
|
|
|
createAudioContext(src) {
|
|
if (this._unloaded) return null
|
|
if (this.audioContext) this.destroyAudioContext()
|
|
const context = wx.createInnerAudioContext()
|
|
const generation = this._contextGeneration + 1
|
|
this._contextGeneration = generation
|
|
this.audioContext = context
|
|
context.autoplay = false
|
|
context.src = src
|
|
this.setData({
|
|
audioReady: true,
|
|
loading: false,
|
|
playing: false,
|
|
error: '',
|
|
})
|
|
this.configureRateControls(context, generation)
|
|
|
|
context.onCanplay(() => {
|
|
if (!this.isCurrentContext(context, generation)) return
|
|
this.setData({ audioReady: true, error: '' })
|
|
})
|
|
context.onPlay(() => {
|
|
if (!this.isCurrentContext(context, generation)) return
|
|
if (
|
|
!this._desiredPlayback
|
|
|| this._activePlayIntentGeneration !== this._intentGeneration
|
|
) {
|
|
context.pause()
|
|
this._controlLocked = false
|
|
this.setData({ playing: false, loading: false })
|
|
return
|
|
}
|
|
this._controlLocked = false
|
|
this.setData({
|
|
playing: true,
|
|
loading: false,
|
|
hasStarted: true,
|
|
error: '',
|
|
})
|
|
})
|
|
context.onPause(() => {
|
|
if (!this.isCurrentContext(context, generation)) return
|
|
if (this._desiredPlayback) return
|
|
this.releasePauseLockFromEvent(context, generation)
|
|
this.setData({ playing: false, loading: false })
|
|
})
|
|
context.onStop(() => {
|
|
if (!this.isCurrentContext(context, generation)) return
|
|
this.invalidatePlaybackIntent(false)
|
|
this.setData({ playing: false, loading: false })
|
|
})
|
|
context.onWaiting(() => {
|
|
if (
|
|
!this.isCurrentContext(context, generation)
|
|
|| !this._desiredPlayback
|
|
) return
|
|
this.setData({ playing: false, loading: true })
|
|
})
|
|
context.onEnded(() => {
|
|
if (!this.isCurrentContext(context, generation)) return
|
|
this.invalidatePlaybackIntent(false)
|
|
this.setProgress(this.data.durationSeconds, this.data.durationSeconds)
|
|
this.setData({ playing: false, loading: false })
|
|
})
|
|
context.onTimeUpdate(() => {
|
|
if (
|
|
!this.isCurrentContext(context, generation)
|
|
|| this._scrubbing
|
|
|| this._pendingSeek
|
|
) return
|
|
const duration = Number(context.duration) || this.data.durationSeconds
|
|
const current = Number(context.currentTime) || 0
|
|
this.setProgress(current, duration)
|
|
})
|
|
if (typeof context.onSeeked === 'function') {
|
|
context.onSeeked(() => {
|
|
if (!this.isCurrentContext(context, generation)) return
|
|
this.finishSeekFromEvent(context, generation)
|
|
})
|
|
}
|
|
context.onError(() => {
|
|
if (!this.isCurrentContext(context, generation)) return
|
|
this.invalidatePlaybackIntent(false)
|
|
this.setData({
|
|
playing: false,
|
|
loading: false,
|
|
error: '声音暂时没打开,您可以返回继续看连环画。',
|
|
})
|
|
})
|
|
this.bindAudioInterruptionHandlers()
|
|
return context
|
|
},
|
|
|
|
beginPlaybackIntent() {
|
|
this._intentGeneration += 1
|
|
this._desiredPlayback = true
|
|
this._activePlayIntentGeneration = this._intentGeneration
|
|
return this._intentGeneration
|
|
},
|
|
|
|
clearSeekState(updateData = true) {
|
|
if (this._seekTimer !== null) {
|
|
clearTimeout(this._seekTimer)
|
|
this._seekTimer = null
|
|
}
|
|
this._seekOperationGeneration += 1
|
|
this._pendingSeek = null
|
|
this._scrubbing = false
|
|
if (updateData && !this._unloaded) this.setData({ seekLocked: false })
|
|
},
|
|
|
|
clearPauseLock(unlock = true) {
|
|
if (this._pauseLockTimer !== null) {
|
|
clearTimeout(this._pauseLockTimer)
|
|
this._pauseLockTimer = null
|
|
}
|
|
this._controlOperationGeneration += 1
|
|
this._pendingPauseLock = null
|
|
if (unlock) this._controlLocked = false
|
|
},
|
|
|
|
beginPauseLock(context) {
|
|
this.clearPauseLock(true)
|
|
const operationGeneration = this._controlOperationGeneration + 1
|
|
this._controlOperationGeneration = operationGeneration
|
|
const pending = {
|
|
operationGeneration,
|
|
contextGeneration: this._contextGeneration,
|
|
intentGeneration: this._intentGeneration,
|
|
lifecycleGeneration: this._lifecycleGeneration,
|
|
context,
|
|
}
|
|
this._pendingPauseLock = pending
|
|
this._controlLocked = true
|
|
this._pauseLockTimer = setTimeout(() => {
|
|
this.releasePauseLock(
|
|
operationGeneration,
|
|
pending.contextGeneration,
|
|
pending.intentGeneration,
|
|
pending.lifecycleGeneration,
|
|
context,
|
|
)
|
|
}, PAUSE_LOCK_TIMEOUT_MS)
|
|
},
|
|
|
|
releasePauseLock(
|
|
operationGeneration,
|
|
contextGeneration,
|
|
intentGeneration,
|
|
lifecycleGeneration,
|
|
context,
|
|
) {
|
|
const pending = this._pendingPauseLock
|
|
if (
|
|
!pending
|
|
|| pending.operationGeneration !== operationGeneration
|
|
|| pending.contextGeneration !== contextGeneration
|
|
|| pending.intentGeneration !== intentGeneration
|
|
|| pending.lifecycleGeneration !== lifecycleGeneration
|
|
|| pending.context !== context
|
|
|| !this.isCurrentContext(context, contextGeneration)
|
|
|| this._intentGeneration !== intentGeneration
|
|
|| this._lifecycleGeneration !== lifecycleGeneration
|
|
|| this._desiredPlayback
|
|
) return false
|
|
if (this._pauseLockTimer !== null) {
|
|
clearTimeout(this._pauseLockTimer)
|
|
this._pauseLockTimer = null
|
|
}
|
|
this._pendingPauseLock = null
|
|
this._controlLocked = false
|
|
return true
|
|
},
|
|
|
|
releasePauseLockFromEvent(context, contextGeneration) {
|
|
const pending = this._pendingPauseLock
|
|
if (!pending) {
|
|
if (this.isCurrentContext(context, contextGeneration)) {
|
|
this._controlLocked = false
|
|
}
|
|
return
|
|
}
|
|
this.releasePauseLock(
|
|
pending.operationGeneration,
|
|
contextGeneration,
|
|
pending.intentGeneration,
|
|
pending.lifecycleGeneration,
|
|
context,
|
|
)
|
|
},
|
|
|
|
invalidatePlaybackIntent(pauseContext = true) {
|
|
this._intentGeneration += 1
|
|
this._activePlayIntentGeneration = 0
|
|
this._desiredPlayback = false
|
|
this.clearPauseLock(true)
|
|
this.clearSeekState(!this._unloaded)
|
|
if (
|
|
pauseContext
|
|
&& this.audioContext
|
|
&& typeof this.audioContext.pause === 'function'
|
|
) {
|
|
this.audioContext.pause()
|
|
}
|
|
},
|
|
|
|
startPlaybackWithIntent(intentGeneration) {
|
|
const context = this.audioContext
|
|
if (
|
|
!context
|
|
|| this._unloaded
|
|
|| intentGeneration !== this._intentGeneration
|
|
|| !this._desiredPlayback
|
|
) return false
|
|
this._controlLocked = true
|
|
this.setData({
|
|
loading: true,
|
|
playing: false,
|
|
hasStarted: true,
|
|
error: '',
|
|
})
|
|
context.play()
|
|
return true
|
|
},
|
|
|
|
bindAudioInterruptionHandlers() {
|
|
if (this._audioInterruptionBeginHandler) return
|
|
const generation = this._lifecycleGeneration
|
|
this._audioInterruptionBeginHandler = () => {
|
|
if (this._unloaded || this._lifecycleGeneration !== generation) return
|
|
this.pauseWithoutAutoResume()
|
|
}
|
|
this._audioInterruptionEndHandler = () => {
|
|
if (this._unloaded || this._lifecycleGeneration !== generation) return
|
|
this.invalidatePlaybackIntent(false)
|
|
this.setData({ playing: false, loading: false })
|
|
}
|
|
if (typeof wx.onAudioInterruptionBegin === 'function') {
|
|
wx.onAudioInterruptionBegin(this._audioInterruptionBeginHandler)
|
|
}
|
|
if (typeof wx.onAudioInterruptionEnd === 'function') {
|
|
wx.onAudioInterruptionEnd(this._audioInterruptionEndHandler)
|
|
}
|
|
},
|
|
|
|
unbindAudioInterruptionHandlers() {
|
|
if (
|
|
this._audioInterruptionBeginHandler
|
|
&& typeof wx.offAudioInterruptionBegin === 'function'
|
|
) {
|
|
wx.offAudioInterruptionBegin(this._audioInterruptionBeginHandler)
|
|
}
|
|
if (
|
|
this._audioInterruptionEndHandler
|
|
&& typeof wx.offAudioInterruptionEnd === 'function'
|
|
) {
|
|
wx.offAudioInterruptionEnd(this._audioInterruptionEndHandler)
|
|
}
|
|
this._audioInterruptionBeginHandler = null
|
|
this._audioInterruptionEndHandler = null
|
|
},
|
|
|
|
pauseWithoutAutoResume() {
|
|
const context = this.audioContext
|
|
this.invalidatePlaybackIntent(false)
|
|
if (context && typeof context.pause === 'function') {
|
|
this.beginPauseLock(context)
|
|
try {
|
|
context.pause()
|
|
} catch (_) {
|
|
this.clearPauseLock(true)
|
|
}
|
|
}
|
|
if (!this._unloaded) {
|
|
this.setData({ playing: false, loading: false })
|
|
}
|
|
},
|
|
|
|
onHide() {
|
|
this.pauseWithoutAutoResume()
|
|
},
|
|
|
|
onShow() {
|
|
if (this._unloaded) return
|
|
this.clearPauseLock(true)
|
|
this.setData({ playing: false, loading: false, seekLocked: false })
|
|
},
|
|
|
|
togglePlayback() {
|
|
if (this.data.loading || this._controlLocked || this._unloaded) return
|
|
if (!this.audioContext && this._page) {
|
|
this.createAudioContext(this._page.audioSrc)
|
|
}
|
|
if (!this.audioContext) return
|
|
if (this.data.playing || this._desiredPlayback) {
|
|
this.pauseWithoutAutoResume()
|
|
return
|
|
}
|
|
const intentGeneration = this.beginPlaybackIntent()
|
|
this.startPlaybackWithIntent(intentGeneration)
|
|
},
|
|
|
|
requestSeek(targetValue, options = {}) {
|
|
const context = this.audioContext
|
|
if (
|
|
!context
|
|
|| this._unloaded
|
|
|| this.data.loading
|
|
|| this._pendingSeek
|
|
) return false
|
|
const duration = Math.max(
|
|
1,
|
|
Number(context.duration) || Number(this.data.durationSeconds) || 1,
|
|
)
|
|
const target = clamp(targetValue, 0, duration)
|
|
const actualBeforeSeek = Number(context.currentTime)
|
|
const previousValue = clamp(
|
|
Number.isFinite(actualBeforeSeek)
|
|
? actualBeforeSeek
|
|
: Number(this.data.sliderValue) || 0,
|
|
0,
|
|
duration,
|
|
)
|
|
const operationGeneration = this._seekOperationGeneration + 1
|
|
this._seekOperationGeneration = operationGeneration
|
|
const pending = {
|
|
operationGeneration,
|
|
contextGeneration: this._contextGeneration,
|
|
intentGeneration: this._intentGeneration,
|
|
context,
|
|
target,
|
|
previousValue,
|
|
playAfterSeek: Boolean(options.playAfterSeek),
|
|
}
|
|
this._pendingSeek = pending
|
|
this._scrubbing = false
|
|
this.setProgress(target, duration)
|
|
this.setData({ seekLocked: true, error: '' })
|
|
this._seekTimer = setTimeout(() => {
|
|
this.failSeek(
|
|
operationGeneration,
|
|
pending.contextGeneration,
|
|
pending.intentGeneration,
|
|
context,
|
|
)
|
|
}, SEEK_TIMEOUT_MS)
|
|
try {
|
|
context.seek(target)
|
|
} catch (_) {
|
|
this.failSeek(
|
|
operationGeneration,
|
|
pending.contextGeneration,
|
|
pending.intentGeneration,
|
|
context,
|
|
)
|
|
return false
|
|
}
|
|
return true
|
|
},
|
|
|
|
finishSeekFromEvent(context, contextGeneration) {
|
|
const pending = this._pendingSeek
|
|
if (!pending) return
|
|
const actual = Number(context.currentTime)
|
|
if (Number.isFinite(actual) && Math.abs(actual - pending.target) > 1.25) return
|
|
this.finishSeek(
|
|
pending.operationGeneration,
|
|
contextGeneration,
|
|
pending.intentGeneration,
|
|
context,
|
|
)
|
|
},
|
|
|
|
finishSeek(
|
|
operationGeneration,
|
|
contextGeneration,
|
|
intentGeneration,
|
|
context,
|
|
) {
|
|
const pending = this._pendingSeek
|
|
if (
|
|
!pending
|
|
|| pending.operationGeneration !== operationGeneration
|
|
|| pending.contextGeneration !== contextGeneration
|
|
|| pending.intentGeneration !== intentGeneration
|
|
|| pending.context !== context
|
|
|| !this.isCurrentContext(context, contextGeneration)
|
|
|| this._intentGeneration !== intentGeneration
|
|
) return
|
|
if (this._seekTimer !== null) {
|
|
clearTimeout(this._seekTimer)
|
|
this._seekTimer = null
|
|
}
|
|
this._pendingSeek = null
|
|
this.setData({ seekLocked: false })
|
|
if (
|
|
pending.playAfterSeek
|
|
&& this._desiredPlayback
|
|
&& this._activePlayIntentGeneration === intentGeneration
|
|
) {
|
|
this.startPlaybackWithIntent(intentGeneration)
|
|
}
|
|
},
|
|
|
|
failSeek(
|
|
operationGeneration,
|
|
contextGeneration,
|
|
intentGeneration,
|
|
context,
|
|
) {
|
|
const pending = this._pendingSeek
|
|
if (
|
|
!pending
|
|
|| pending.operationGeneration !== operationGeneration
|
|
|| pending.contextGeneration !== contextGeneration
|
|
|| pending.intentGeneration !== intentGeneration
|
|
|| pending.context !== context
|
|
|| !this.isCurrentContext(context, contextGeneration)
|
|
|| this._intentGeneration !== intentGeneration
|
|
) return false
|
|
if (this._seekTimer !== null) {
|
|
clearTimeout(this._seekTimer)
|
|
this._seekTimer = null
|
|
}
|
|
this._pendingSeek = null
|
|
this._scrubbing = false
|
|
const actual = Number(context.currentTime)
|
|
const restoredValue = Number.isFinite(actual)
|
|
? actual
|
|
: pending.previousValue
|
|
if (pending.playAfterSeek) this.invalidatePlaybackIntent(false)
|
|
this.setProgress(restoredValue, this.data.durationSeconds)
|
|
this.setData({
|
|
playing: pending.playAfterSeek ? false : this.data.playing,
|
|
loading: false,
|
|
seekLocked: false,
|
|
error: '进度没有调好,请再试一次。',
|
|
})
|
|
return true
|
|
},
|
|
|
|
previewSeek(event) {
|
|
if (!this.audioContext || this.data.loading || this._pendingSeek) return
|
|
this._scrubbing = true
|
|
this.setProgress(eventValue(event), this.data.durationSeconds)
|
|
},
|
|
|
|
commitSeek(event) {
|
|
if (!this.audioContext || this.data.loading || this._pendingSeek) return
|
|
this._scrubbing = false
|
|
this.requestSeek(eventValue(event))
|
|
},
|
|
|
|
rewindTenSeconds() {
|
|
if (!this.audioContext || this.data.loading || this._pendingSeek) return
|
|
const current = Number(this.audioContext.currentTime)
|
|
const fallback = Number(this.data.sliderValue) || 0
|
|
this.requestSeek(Math.max(0, (Number.isFinite(current) ? current : fallback) - 10))
|
|
},
|
|
|
|
replay() {
|
|
if (
|
|
!this.audioContext
|
|
|| this.data.loading
|
|
|| this._controlLocked
|
|
|| this._pendingSeek
|
|
|| this._unloaded
|
|
) return
|
|
const alreadyPlaying = this.data.playing && this._desiredPlayback
|
|
const intentGeneration = alreadyPlaying
|
|
? this._intentGeneration
|
|
: this.beginPlaybackIntent()
|
|
this.setData({ hasStarted: true, error: '' })
|
|
this.requestSeek(0, {
|
|
playAfterSeek: !alreadyPlaying,
|
|
intentGeneration,
|
|
})
|
|
},
|
|
|
|
changePlaybackRate(event) {
|
|
const requested = Number(
|
|
event && event.currentTarget && event.currentTarget.dataset.rate,
|
|
)
|
|
if (
|
|
!RATE_VALUES.includes(requested)
|
|
|| !this.audioContext
|
|
|| !this.data.rateControlsVisible
|
|
) return
|
|
const allowed = this.data.rateOptions.some((item) => item.value === requested)
|
|
if (!allowed) return
|
|
try {
|
|
this.audioContext.playbackRate = requested
|
|
if (Math.abs(Number(this.audioContext.playbackRate) - requested) > 0.001) {
|
|
this.disableRateControls(this.audioContext)
|
|
return
|
|
}
|
|
this.setData({ playbackRate: requested })
|
|
} catch (_) {
|
|
this.disableRateControls(this.audioContext)
|
|
}
|
|
},
|
|
|
|
goBack() {
|
|
const pages = typeof getCurrentPages === 'function' ? getCurrentPages() : []
|
|
const returnToChapter = () => {
|
|
if (typeof wx.reLaunch === 'function') {
|
|
wx.reLaunch({
|
|
url: '/package-game/pages/chapter/chapter?chapter=1',
|
|
})
|
|
}
|
|
}
|
|
if (pages.length > 1 && typeof wx.navigateBack === 'function') {
|
|
wx.navigateBack({ delta: 1, fail: returnToChapter })
|
|
return
|
|
}
|
|
returnToChapter()
|
|
},
|
|
|
|
destroyAudioContext() {
|
|
const context = this.audioContext
|
|
if (!context) return
|
|
this.invalidatePlaybackIntent(false)
|
|
this._contextGeneration += 1
|
|
this.audioContext = null
|
|
context.destroy()
|
|
if (!this._unloaded) {
|
|
this.setData({
|
|
audioReady: false,
|
|
playing: false,
|
|
loading: false,
|
|
rateOptions: FALLBACK_RATE_OPTIONS,
|
|
rateControlsVisible: false,
|
|
playbackRate: 1,
|
|
})
|
|
}
|
|
},
|
|
|
|
onUnload() {
|
|
this._unloaded = true
|
|
this._lifecycleGeneration += 1
|
|
this._intentGeneration += 1
|
|
this._activePlayIntentGeneration = 0
|
|
this._desiredPlayback = false
|
|
this._controlLocked = true
|
|
this.clearPauseLock(false)
|
|
this.clearSeekState(false)
|
|
this.unbindAudioInterruptionHandlers()
|
|
const context = this.audioContext
|
|
this._contextGeneration += 1
|
|
this.audioContext = null
|
|
if (context) context.destroy()
|
|
},
|
|
})
|