Skip to content

Commit

Permalink
refactor(seek_bar): normalize behavior to support VoD and live contents
Browse files Browse the repository at this point in the history
After this commit, the default behavior is:

* Position and duration values are updated with media metadata only after the media is started;
* Disables interaction with the plugin for live content without DVR after the media started;
* If the current media is updated, sets the position and duration default values and enables interaction again.
  • Loading branch information
joaopaulovieira committed May 22, 2021
1 parent 73f3b74 commit 07c0794
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 66 deletions.
41 changes: 22 additions & 19 deletions src/components/seek_bar/seek_bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import MediaControlComponentPlugin from '../../base/media_control_component/medi

import pluginStyle from './public/style.scss'

export const INITIAL_POSITION = 0
export const INITIAL_DURATION = 100

export default class SeekBarPlugin extends MediaControlComponentPlugin {
get name() { return 'seek_bar' }

Expand All @@ -20,10 +23,10 @@ export default class SeekBarPlugin extends MediaControlComponentPlugin {

get attributes() {
return {
class: 'seek-bar',
class: 'seek-bar seek-bar--disable-interaction',
type: 'range',
value: 0,
max: 100,
value: INITIAL_POSITION,
max: INITIAL_DURATION,
}
}

Expand Down Expand Up @@ -57,7 +60,9 @@ export default class SeekBarPlugin extends MediaControlComponentPlugin {
}

onContainerChanged() {
this.setDefaultProperties()
this.container && this.stopListening(this.container)
this.playback && this.stopListening(this.playback)
this.container = this.core.activeContainer
this.playback = this.core.activePlayback
if (!this.container) return
Expand All @@ -66,12 +71,7 @@ export default class SeekBarPlugin extends MediaControlComponentPlugin {
}

bindContainerEvents() {
const containerEventListenerData = [
{ object: this.container, event: Events.CONTAINER_TIMEUPDATE, callback: this.onTimeUpdate },
{ object: this.container, event: Events.CONTAINER_PROGRESS, callback: this.onContainerProgress },
{ object: this.container, event: Events.CONTAINER_DESTROYED, callback: this.onContainerDestroyed },
]
this.container && containerEventListenerData.forEach(item => this.listenTo(item.object, item.event, item.callback))
this.listenTo(this.container, Events.CONTAINER_PROGRESS, this.onContainerProgress)
}

onTimeUpdate(time) {
Expand Down Expand Up @@ -106,19 +106,16 @@ export default class SeekBarPlugin extends MediaControlComponentPlugin {
this.$el[0].style.setProperty('--buffered-width', `${buffered / duration * 100}%`)
}

onContainerDestroyed() {
this.$el[0].classList.remove('seek-bar--disable-interaction')
}

bindPlaybackEvents() {
const playbackEventListenerData = [{ object: this.playback, event: Events.PLAYBACK_PLAY, callback: this.updateStyles }]
this.playback && playbackEventListenerData.forEach(item => this.listenTo(item.object, item.event, item.callback))
this.listenToOnce(this.playback, Events.PLAYBACK_PLAY, this.onFirstPlay)
}

updateStyles() {
this.shouldDisableInteraction
? this.$el[0].classList.add('seek-bar--disable-interaction')
: this.$el[0].classList.remove('seek-bar--disable-interaction')
onFirstPlay() {
if (!this.shouldDisableInteraction) {
this.$el[0].classList.remove('seek-bar--disable-interaction')
return this.listenTo(this.container, Events.CONTAINER_TIMEUPDATE, this.onTimeUpdate)
}
this.$el[0].value = this.$el[0].max // Fix bar at end for Live medias without DVR
}

updateProgressBarViaInteraction(rangeInput) {
Expand All @@ -144,4 +141,10 @@ export default class SeekBarPlugin extends MediaControlComponentPlugin {
this.isRendered = true
super.render()
}

setDefaultProperties() {
this.$el[0].value = INITIAL_POSITION
this.$el[0].max = INITIAL_DURATION
this.$el[0].classList.add('seek-bar--disable-interaction')
}
}
112 changes: 65 additions & 47 deletions src/components/seek_bar/seek_bar.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Browser, Events, Core, Container, Playback } from '@clappr/core'
import SeekBarPlugin from './seek_bar'
import SeekBarPlugin, { INITIAL_POSITION, INITIAL_DURATION } from './seek_bar'
import MediaControlComponentPlugin from '../../base/media_control_component/media_control_component'

const setupTest = (options = {}, fullSetup = false) => {
Expand Down Expand Up @@ -89,10 +89,10 @@ describe('SeekBarPlugin', function() {
})

test('attributes getter returns all attributes that will be added on the plugin DOM element', () => {
expect(this.plugin.$el[0].className).toEqual('seek-bar media-control__elements')
expect(this.plugin.$el[0].className).toEqual('seek-bar seek-bar--disable-interaction media-control__elements')
expect(this.plugin.$el[0].type).toEqual('range')
expect(this.plugin.$el[0].value).toEqual('0')
expect(this.plugin.$el[0].max).toEqual('100')
expect(this.plugin.$el[0].value).toEqual(`${INITIAL_POSITION}`)
expect(this.plugin.$el[0].max).toEqual(`${INITIAL_DURATION}`)
})

test('have a getter called events', () => {
Expand Down Expand Up @@ -139,7 +139,7 @@ describe('SeekBarPlugin', function() {
expect(Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this.plugin), 'shouldDisableInteraction').get).toBeTruthy()
})

test('shouldDisableInteraction getter informs if the media needs to be stopped instead paused', () => {
test('shouldDisableInteraction getter informs if the plugin needs to be static', () => {
expect(this.plugin.shouldDisableInteraction).toBeFalsy()

jest.spyOn(Playback.prototype, 'getPlaybackType').mockReturnValueOnce(Playback.LIVE)
Expand Down Expand Up @@ -187,13 +187,27 @@ describe('SeekBarPlugin', function() {
})

describe('onContainerChanged method', () => {
test('calls setDefaultProperties method', () => {
jest.spyOn(this.plugin, 'setDefaultProperties')
this.plugin.onContainerChanged()

expect(this.plugin.setDefaultProperties).toHaveBeenCalledTimes(1)
})

test('removes all listeners from old container reference', () => {
jest.spyOn(this.plugin, 'stopListening')
this.plugin.onContainerChanged()

expect(this.plugin.stopListening).toHaveBeenCalledWith(this.container)
})

test('removes all listeners from old playback reference', () => {
jest.spyOn(this.plugin, 'stopListening')
this.plugin.onContainerChanged()

expect(this.plugin.stopListening).toHaveBeenCalledWith(this.plugin.playback)
})

test('saves core.activeContainer reference locally', () => {
this.plugin.onContainerChanged()

Expand Down Expand Up @@ -237,18 +251,10 @@ describe('SeekBarPlugin', function() {
})

test('avoid register callback for events on container scope without a valid reference', () => {
jest.spyOn(this.plugin, 'onTimeUpdate')
this.container.trigger(Events.CONTAINER_TIMEUPDATE)

expect(this.plugin.onTimeUpdate).not.toHaveBeenCalled()
})

test('register onTimeUpdate method as callback for CONTAINER_TIMEUPDATE event', () => {
jest.spyOn(this.plugin, 'onTimeUpdate')
this.core.activeContainer = this.container
this.container.trigger(Events.CONTAINER_TIMEUPDATE)
jest.spyOn(this.plugin, 'onContainerProgress')
this.container.trigger(Events.CONTAINER_PROGRESS)

expect(this.plugin.onTimeUpdate).toHaveBeenCalledTimes(1)
expect(this.plugin.onContainerProgress).not.toHaveBeenCalled()
})

test('register onContainerProgress method as callback for CONTAINER_PROGRESS event', () => {
Expand All @@ -258,14 +264,6 @@ describe('SeekBarPlugin', function() {

expect(this.plugin.onContainerProgress).toHaveBeenCalledTimes(1)
})

test('register onContainerDestroyed method as callback for CONTAINER_DESTROYED event', () => {
jest.spyOn(this.plugin, 'onContainerDestroyed')
this.core.activeContainer = this.container
this.container.trigger(Events.CONTAINER_DESTROYED)

expect(this.plugin.onContainerDestroyed).toHaveBeenCalledTimes(1)
})
})

describe('onTimeUpdate callback', () => {
Expand Down Expand Up @@ -357,48 +355,48 @@ describe('SeekBarPlugin', function() {
})
})

describe('onContainerDestroyed method', () => {
test('removes seek-bar--disable-interaction css class to DOM element plugin', () => {
jest.spyOn(this.plugin, 'shouldDisableInteraction', 'get').mockReturnValueOnce(true)
this.plugin.updateStyles()

expect(this.plugin.$el[0].classList.contains('seek-bar--disable-interaction')).toBeTruthy()

this.plugin.onContainerDestroyed()
expect(this.plugin.$el[0].classList.contains('seek-bar--disable-interaction')).toBeFalsy()
})
})

describe('bindPlaybackEvents method', () => {
test('avoid register callback for events on playback scope without a valid reference', () => {
jest.spyOn(this.plugin, 'updateStyles')
jest.spyOn(this.plugin, 'onFirstPlay')
this.playback.trigger(Events.PLAYBACK_PLAY)

expect(this.plugin.updateStyles).not.toHaveBeenCalled()
expect(this.plugin.onFirstPlay).not.toHaveBeenCalled()
})

test('register updateStyles method as callback for PLAYBACK_PLAY event', () => {
jest.spyOn(this.plugin, 'updateStyles')
test('register onFirstPlay method as callback for PLAYBACK_PLAY event', () => {
jest.spyOn(this.plugin, 'onFirstPlay')
this.core.activeContainer = this.container
this.playback.trigger(Events.PLAYBACK_PLAY)

expect(this.plugin.updateStyles).toHaveBeenCalledTimes(1)
expect(this.plugin.onFirstPlay).toHaveBeenCalledTimes(1)
})
})

describe('updateStyles method', () => {
test('adds seek-bar--disable-interaction css class to DOM element plugin if shouldDisableInteraction getter returns true', () => {
describe('onFirstPlay callback', () => {
test('removes seek-bar--disable-interaction css class to DOM element plugin if shouldDisableInteraction getter returns false', () => {
jest.spyOn(this.plugin, 'shouldDisableInteraction', 'get').mockReturnValueOnce(true)
this.plugin.updateStyles()
this.plugin.onFirstPlay()

expect(this.plugin.$el[0].classList.contains('seek-bar--disable-interaction')).toBeTruthy()
})

test('removes seek-bar--disable-interaction css class to DOM element plugin if shouldDisableInteraction getter returns false', () => {
test('register onFirstPlay method as callback for CONTAINER_TIMEUPDATE event if shouldDisableInteraction getter returns false', () => {
jest.spyOn(this.plugin, 'shouldDisableInteraction', 'get').mockReturnValueOnce(false)
this.plugin.updateStyles()
jest.spyOn(this.plugin, 'onTimeUpdate')
this.core.activeContainer = this.container

this.plugin.onFirstPlay()
this.container.trigger(Events.CONTAINER_TIMEUPDATE)

expect(this.plugin.$el[0].classList.contains('seek-bar--disable-interaction')).toBeFalsy()
expect(this.plugin.onTimeUpdate).toHaveBeenCalledTimes(1)
})

test('sets full filled seek bar if shouldDisableInteraction getter returns true', () => {
jest.spyOn(this.plugin, 'shouldDisableInteraction', 'get').mockReturnValueOnce(true)
this.core.activeContainer = this.container
this.plugin.onFirstPlay()

expect(this.plugin.$el[0].value).toEqual(this.plugin.$el[0].max)
})
})

Expand Down Expand Up @@ -494,4 +492,24 @@ describe('SeekBarPlugin', function() {
expect(this.plugin.isRendered).toBeTruthy()
})
})

describe('setDefaultProperties method', () => {
test('sets INITIAL_POSITION as seek bar current position', () => {
this.plugin.setDefaultProperties()

expect(this.plugin.$el[0].value).toEqual(`${INITIAL_POSITION}`)
})

test('sets INITIAL_DURATION as seek bar max value', () => {
this.plugin.setDefaultProperties()

expect(this.plugin.$el[0].max).toEqual(`${INITIAL_DURATION}`)
})

test('adds seek-bar--disable-interaction css class to DOM element plugin', () => {
this.plugin.setDefaultProperties()

expect(this.plugin.$el[0].classList.contains('seek-bar--disable-interaction')).toBeTruthy()
})
})
})

0 comments on commit 07c0794

Please sign in to comment.