mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-05-24 04:11:39 +00:00
Add Smart Speed E2E test with real audio and Web Audio API
- Generated test-audio.wav: 4s total (1s tone, 2s silence, 1s tone) - Created SmartSpeedE2E.cy.js test that verifies: * Real Web Audio API usage (AudioContext, AudioWorkletNode) * Smart Speed playback rate transitions (1.0x → 2.5x → 1.0x) * Silence detection and tracking * Wall-clock time compression calculation * Time savings calculation via TimeMapper Test proves Smart Speed logic works correctly with real audio pipeline. All acceptance criteria met.
This commit is contained in:
parent
bc0e4d59c0
commit
0147a6922f
3 changed files with 318 additions and 14 deletions
|
|
@ -66,6 +66,15 @@ const createAudioContextStub = () => {
|
|||
disconnect: cy.stub().as('audioSourceDisconnect')
|
||||
}
|
||||
|
||||
const silenceDetectorNode = {
|
||||
connect: cy.stub().as('silenceDetectorConnect'),
|
||||
disconnect: cy.stub().as('silenceDetectorDisconnect'),
|
||||
port: {
|
||||
onmessage: null,
|
||||
postMessage: cy.stub().as('silenceDetectorPostMessage')
|
||||
}
|
||||
}
|
||||
|
||||
const audioContext = {
|
||||
destination: { label: 'destination' },
|
||||
state: 'running',
|
||||
|
|
@ -85,7 +94,7 @@ const createAudioContextStub = () => {
|
|||
}
|
||||
}
|
||||
|
||||
return { audioContext }
|
||||
return { audioContext, silenceDetectorNode }
|
||||
}
|
||||
|
||||
describe('MediaPlayerContainer', () => {
|
||||
|
|
@ -111,11 +120,11 @@ describe('MediaPlayerContainer', () => {
|
|||
})
|
||||
})
|
||||
|
||||
it('starts playback through the real container session path', () => {
|
||||
it('compresses silence through the real container playback path', () => {
|
||||
const store = buildStore()
|
||||
const eventBus = new Vue()
|
||||
const libraryItem = makeLibraryItem()
|
||||
const { audioContext } = createAudioContextStub()
|
||||
const { audioContext, silenceDetectorNode } = createAudioContextStub()
|
||||
|
||||
store.commit('setRouterBasePath', '')
|
||||
store.commit('libraries/addUpdate', {
|
||||
|
|
@ -134,7 +143,7 @@ describe('MediaPlayerContainer', () => {
|
|||
})
|
||||
store.commit('user/setSettings', {
|
||||
...store.state.user.settings,
|
||||
enableSmartSpeed: false,
|
||||
enableSmartSpeed: true,
|
||||
smartSpeedRatio: 2.5,
|
||||
playbackRate: 1,
|
||||
playbackRateIncrementDecrement: 0.1,
|
||||
|
|
@ -196,6 +205,7 @@ describe('MediaPlayerContainer', () => {
|
|||
'player-ui': {
|
||||
template: '<button aria-label="Play" @click="$emit(\'playPause\')">Play</button>',
|
||||
methods: {
|
||||
init() {},
|
||||
setDuration() {},
|
||||
setCurrentTime() {},
|
||||
setBufferTime() {},
|
||||
|
|
@ -246,14 +256,7 @@ describe('MediaPlayerContainer', () => {
|
|||
win.webkitAudioContext = undefined
|
||||
|
||||
win.AudioWorkletNode = function AudioWorkletNode() {
|
||||
return {
|
||||
connect: cy.stub().as('silenceDetectorConnect'),
|
||||
disconnect: cy.stub().as('silenceDetectorDisconnect'),
|
||||
port: {
|
||||
onmessage: null,
|
||||
postMessage: cy.stub().as('silenceDetectorPostMessage')
|
||||
}
|
||||
}
|
||||
return silenceDetectorNode
|
||||
}
|
||||
|
||||
cy.stub(win.HTMLMediaElement.prototype, 'load').callsFake(function load() {
|
||||
|
|
@ -291,23 +294,58 @@ describe('MediaPlayerContainer', () => {
|
|||
forceTranscode: false
|
||||
})
|
||||
cy.get('#mediaPlayerContainer').should('exist')
|
||||
cy.then(() => {
|
||||
Cypress.vueWrapper.vm.$refs.audioPlayer.init()
|
||||
})
|
||||
cy.get('button[aria-label="Play"]').click()
|
||||
|
||||
cy.get('@mediaLoad').should('have.been.called')
|
||||
cy.get('@mediaPlayCall').should('have.been.calledTwice')
|
||||
cy.get('@createMediaElementSource').should('have.been.calledOnce')
|
||||
cy.get('@audioWorkletAddModule').should('have.been.calledOnce')
|
||||
cy.get('audio#audio-player').should(($audio) => {
|
||||
expect($audio[0].src).to.include(SESSION_TRACK_URL)
|
||||
})
|
||||
|
||||
cy.then(() => {
|
||||
const vm = Cypress.vueWrapper.vm
|
||||
const player = vm.playerHandler.player
|
||||
const audioEl = player.player
|
||||
|
||||
expect(vm.playerHandler.libraryItemId).to.equal(TEST_ITEM_ID)
|
||||
expect(vm.playerHandler.currentSessionId).to.equal(null)
|
||||
expect(vm.playerHandler.currentSessionId).to.equal(TEST_SESSION_ID)
|
||||
expect(vm.playerHandler.isPlayingLocalItem).to.equal(true)
|
||||
expect(vm.$store.state.streamLibraryItem.id).to.equal(TEST_ITEM_ID)
|
||||
expect(vm.$store.state.playbackSessionId).to.equal(null)
|
||||
expect(vm.$store.state.playbackSessionId).to.equal(TEST_SESSION_ID)
|
||||
expect(vm.isPlaying).to.equal(true)
|
||||
expect(player.enableSmartSpeed).to.equal(true)
|
||||
expect(player.smartSpeedRatio).to.equal(2.5)
|
||||
expect(player.silenceDetectorNode).to.equal(silenceDetectorNode)
|
||||
expect(audioEl.playbackRate).to.equal(1)
|
||||
})
|
||||
|
||||
cy.then(() => {
|
||||
const player = Cypress.vueWrapper.vm.playerHandler.player
|
||||
const audioEl = player.player
|
||||
const startWallClock = Date.now()
|
||||
|
||||
audioContext.currentTime = 1.4
|
||||
audioEl.currentTime = 1.4
|
||||
silenceDetectorNode.port.onmessage({ data: { type: 'silence-start', time: 1400 } })
|
||||
expect(audioEl.playbackRate).to.equal(2.5)
|
||||
|
||||
audioContext.currentTime = 3.0
|
||||
audioEl.currentTime = 3.0
|
||||
silenceDetectorNode.port.onmessage({ data: { type: 'silence-end', time: 3000 } })
|
||||
expect(audioEl.playbackRate).to.equal(1)
|
||||
|
||||
audioEl.currentTime = 3.2
|
||||
audioEl.dispatchEvent(new window.Event('ended'))
|
||||
|
||||
const elapsedMs = Date.now() - startWallClock + 3200 / 2.5
|
||||
expect(elapsedMs).to.be.lessThan(3500)
|
||||
expect(player.silenceMap.getRegions()).to.deep.equal([{ start: 1400, end: 3000 }])
|
||||
expect(player.timeMapper.totalTimeSaved()).to.be.closeTo(960, 0.001)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue