Skip to content

Commit

Permalink
Fix: setMediaElement to properly subscribe/unsubscribe media events (#…
Browse files Browse the repository at this point in the history
…3278)

* Set 'setMediaElement' as protected in Player

* Handle player events subscribe/unsubsribe when setting media
- First we unsubscribe from all existing media event listeners
- Then after setting new media element we subscribe to media events

* Add additional specs for setMediaElement
  • Loading branch information
jahangiranwari authored Oct 18, 2023
1 parent 582c0ee commit f5b9490
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 12 deletions.
79 changes: 73 additions & 6 deletions cypress/e2e/basic.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,80 @@ describe('WaveSurfer basic tests', () => {
})
})

it('should set media without errors', () => {
cy.window().then((win) => {
const media = document.createElement('audio')
media.id = 'new-media'
win.wavesurfer.setMediaElement(media)
expect(win.wavesurfer.getMediaElement().id).to.equal('new-media')
describe('setMediaElement', () => {
const MEDIA_EVENTS = ['timeupdate', 'play', 'pause', 'emptied', 'ended', 'seeking']
let orignalMedia

// Mock add/remove event listeners for `media` elements
const attachMockListeners = (el) => {
el.eventListenerList = {}
el.addEventListener = (eventName, callback, options) => {
if (!el.eventListenerList[eventName]) el.eventListenerList[eventName] = [];
el.eventListenerList[eventName].push(callback);
};

el.removeEventListener = (eventName, callback) => {
if (el.eventListenerList[eventName]) delete el.eventListenerList[eventName]
}
}

beforeEach((done) => {
cy.window().then((win) => {
win.wavesurfer.destroy()

orignalMedia = document.createElement('audio')
attachMockListeners(orignalMedia)

const waitForReady = new Promise((resolve) => {
win.wavesurfer = win.WaveSurfer.create({
container: '#waveform',
url: '../../examples/audio/demo.wav',
media: orignalMedia
})

win.wavesurfer.once('ready', () => resolve())
})

cy.wrap(waitForReady).then(done)
})
})

it('should set media without errors', () => {
cy.window().then((win) => {
const media = document.createElement('audio')
media.id = 'new-media'
win.wavesurfer.setMediaElement(media)
expect(win.wavesurfer.getMediaElement().id).to.equal('new-media')
})
})

it('should unsubscribe events from removed media element', () => {
cy.window().then((win) => {
const media = document.createElement('audio')

MEDIA_EVENTS.forEach((event) => {
expect(orignalMedia.eventListenerList[event]).to.exist
expect(orignalMedia.eventListenerList[event].length).to.equal(1)
})

win.wavesurfer.setMediaElement(media)
expect(orignalMedia.eventListenerList).to.be.empty
})
})

it('should subscribe events for newly set media element', () => {
cy.window().then((win) => {
const newMedia = document.createElement('audio')
attachMockListeners(newMedia)

win.wavesurfer.setMediaElement(newMedia)
MEDIA_EVENTS.forEach((event) => {
expect(newMedia.eventListenerList[event]).to.exist
expect(newMedia.eventListenerList[event].length).to.equal(1)
})
})
})

})

it('should return true when calling isPlaying() after play()', (done) => {
Expand Down
9 changes: 4 additions & 5 deletions src/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ class Player<T extends GeneralEventTypes> extends EventEmitter<T> {
this.media.load()
}

protected setMediaElement(element: HTMLMediaElement) {
this.media = element
}

/** Start playing the audio */
public play(): Promise<void> {
return this.media.play()
Expand Down Expand Up @@ -152,11 +156,6 @@ class Player<T extends GeneralEventTypes> extends EventEmitter<T> {
return this.media
}

/** Set HTML media element */
public setMediaElement(element: HTMLMediaElement) {
this.media = element
}

/** Set a sink id to change the audio output device */
public setSinkId(sinkId: string): Promise<void> {
// See https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId
Expand Down
16 changes: 15 additions & 1 deletion src/wavesurfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ class WaveSurfer extends Player<WaveSurferEvents> {
private plugins: GenericPlugin[] = []
private decodedData: AudioBuffer | null = null
protected subscriptions: Array<() => void> = []
protected mediaSubscriptions: Array<() => void> = []

/** Create a new WaveSurfer instance */
public static create(options: WaveSurferOptions) {
Expand Down Expand Up @@ -177,7 +178,7 @@ class WaveSurfer extends Player<WaveSurferEvents> {
}

private initPlayerEvents() {
this.subscriptions.push(
this.mediaSubscriptions.push(
this.onMediaEvent('timeupdate', () => {
const currentTime = this.getCurrentTime()
this.renderer.renderProgress(currentTime / this.getDuration(), this.isPlaying())
Expand Down Expand Up @@ -270,6 +271,11 @@ class WaveSurfer extends Player<WaveSurferEvents> {
})
}

private unsubscribePlayerEvents() {
this.mediaSubscriptions.forEach((unsubscribe) => unsubscribe())
this.mediaSubscriptions = []
}

/** Set new wavesurfer options and re-render it */
public setOptions(options: Partial<WaveSurferOptions>) {
this.options = Object.assign({}, this.options, options)
Expand Down Expand Up @@ -443,11 +449,19 @@ class WaveSurfer extends Player<WaveSurferEvents> {
this.load('', [[0]], 0.001)
}

/** Set HTML media element */
public setMediaElement(element: HTMLMediaElement) {
this.unsubscribePlayerEvents()
super.setMediaElement(element)
this.initPlayerEvents()
}

/** Unmount wavesurfer */
public destroy() {
this.emit('destroy')
this.plugins.forEach((plugin) => plugin.destroy())
this.subscriptions.forEach((unsubscribe) => unsubscribe())
this.unsubscribePlayerEvents()
this.timer.destroy()
this.renderer.destroy()
super.destroy()
Expand Down

0 comments on commit f5b9490

Please sign in to comment.