import Vue from 'vue' import Vuex from 'vuex' import MediaPlayerContainer from '../../../components/app/MediaPlayerContainer.vue' import * as rootStore from '../../../store/index' import * as userStore from '../../../store/user' import * as globalsStore from '../../../store/globals' import * as librariesStore from '../../../store/libraries' Vue.use(Vuex) const FIXTURE_URL = '/__cypress/fixtures/test-audio.wav' const TEST_LIBRARY_ID = 'lib-test' const TEST_ITEM_ID = 'item-test' const TEST_SESSION_ID = 'session-test' const SESSION_TRACK_URL = `/public/session/${TEST_SESSION_ID}/track/0` const makeLibraryItem = () => ({ id: TEST_ITEM_ID, libraryId: TEST_LIBRARY_ID, mediaType: 'book', updatedAt: 1714608000000, media: { coverPath: null, duration: 4, metadata: { title: 'Smart Speed Harness Fixture', authors: [{ id: 'author-1', name: 'Harness Author' }], explicit: false }, chapters: [{ id: 'chapter-1', start: 0, end: 4, title: 'Fixture Chapter' }] } }) const buildStore = () => { return new Vuex.Store({ state: rootStore.state(), getters: rootStore.getters, mutations: rootStore.mutations, modules: { user: { namespaced: true, state: userStore.state(), getters: userStore.getters, mutations: userStore.mutations, actions: userStore.actions }, globals: { namespaced: true, state: globalsStore.state(), getters: globalsStore.getters, mutations: globalsStore.mutations }, libraries: { namespaced: true, state: librariesStore.state(), getters: librariesStore.getters, mutations: librariesStore.mutations } } }) } const createAudioContextStub = () => { const sourceNode = { connect: cy.stub().as('audioSourceConnect'), disconnect: cy.stub().as('audioSourceDisconnect') } const audioContext = { destination: { label: 'destination' }, state: 'running', currentTime: 0, resume: cy.stub().callsFake(() => { audioContext.state = 'running' return Promise.resolve() }).as('audioContextResume'), suspend: cy.stub().callsFake(() => { audioContext.state = 'suspended' return Promise.resolve() }).as('audioContextSuspend'), close: cy.stub().resolves().as('audioContextClose'), createMediaElementSource: cy.stub().returns(sourceNode).as('createMediaElementSource'), audioWorklet: { addModule: cy.stub().resolves().as('audioWorkletAddModule') } } return { audioContext } } describe('MediaPlayerContainer', () => { beforeEach(() => { cy.viewport(1280, 900) cy.window().then((win) => { win.MediaMetadata = function MediaMetadata(metadata) { Object.assign(this, metadata) } const mediaSession = { playbackState: 'none', metadata: null, setActionHandler: cy.stub().as('setActionHandler') } Object.defineProperty(win.navigator, 'mediaSession', { configurable: true, get() { return mediaSession } }) }) }) it('starts playback through the real container session path', () => { const store = buildStore() const eventBus = new Vue() const libraryItem = makeLibraryItem() const { audioContext } = createAudioContextStub() store.commit('setRouterBasePath', '') store.commit('libraries/addUpdate', { id: TEST_LIBRARY_ID, mediaType: 'book', settings: { coverAspectRatio: 0 } }) store.commit('libraries/setCurrentLibrary', { id: TEST_LIBRARY_ID }) store.commit('user/setUser', { id: 'user-1', type: 'root', mediaProgress: [], bookmarks: [], permissions: { update: true, delete: true, download: true, upload: true, accessAllLibraries: true }, librariesAccessible: [TEST_LIBRARY_ID] }) store.commit('user/setSettings', { ...store.state.user.settings, enableSmartSpeed: false, smartSpeedRatio: 2.5, playbackRate: 1, playbackRateIncrementDecrement: 0.1, jumpForwardAmount: 10, jumpBackwardAmount: 10, useChapterTrack: false }) cy.intercept('GET', `/api/items/${TEST_ITEM_ID}?expanded=1`, { statusCode: 200, body: libraryItem }).as('getLibraryItem') cy.intercept('POST', `/api/items/${TEST_ITEM_ID}/play`, (req) => { expect(req.body.mediaPlayer).to.equal('html5') req.reply({ statusCode: 200, body: { id: TEST_SESSION_ID, libraryItem, episodeId: null, displayTitle: 'Smart Speed Harness Fixture', displayAuthor: 'Harness Author', currentTime: 0, playMethod: 0, audioTracks: [ { index: 0, startOffset: 0, duration: 4, contentUrl: FIXTURE_URL, mimeType: 'audio/wav' } ] } }) }).as('startPlaybackSession') cy.intercept('POST', `/api/session/${TEST_SESSION_ID}/close`, { statusCode: 200, body: {} }).as('closePlaybackSession') cy.intercept('POST', `/api/session/${TEST_SESSION_ID}/sync`, { statusCode: 200, body: {} }).as('syncPlaybackSession') cy.mount(MediaPlayerContainer, { store, stubs: { 'covers-book-cover': { template: '
' }, 'ui-tooltip': { template: '