level-selector
Version:
A simple plugin for Clappr that adds support to manually select level on multi-bitrate streams.
173 lines (133 loc) • 5.79 kB
JavaScript
import { Events, Styler, UICorePlugin, template } from 'clappr'
import pluginHtml from './public/level-selector.html'
import pluginStyle from './public/style.scss'
const AUTO = -1
export default class LevelSelector extends UICorePlugin {
static get version() { return VERSION }
get name() { return 'level_selector' }
get template() { return template(pluginHtml) }
get attributes() {
return {
'class': this.name,
'data-level-selector': ''
}
}
get events() {
return {
'click [data-level-selector-select]': 'onLevelSelect',
'click [data-level-selector-button]': 'onShowLevelSelectMenu'
}
}
bindEvents() {
this.listenTo(this.core, Events.CORE_READY, this.bindPlaybackEvents)
this.listenTo(this.core, Events.CORE_ACTIVE_CONTAINER_CHANGED, this.reload)
this.listenTo(this.core.mediaControl, Events.MEDIACONTROL_RENDERED, this.render)
this.listenTo(this.core.mediaControl, Events.MEDIACONTROL_HIDE, this.hideSelectLevelMenu)
}
unBindEvents() {
this.stopListening(this.core, Events.CORE_READY)
this.stopListening(this.core, Events.CORE_ACTIVE_CONTAINER_CHANGED)
this.stopListening(this.core.mediaControl, Events.MEDIACONTROL_RENDERED)
this.stopListening(this.core.mediaControl, Events.MEDIACONTROL_HIDE)
this.stopListening(this.core.activePlayback, Events.PLAYBACK_LEVELS_AVAILABLE)
this.stopListening(this.core.activePlayback, Events.PLAYBACK_LEVEL_SWITCH_START)
this.stopListening(this.core.activePlayback, Events.PLAYBACK_LEVEL_SWITCH_END)
this.stopListening(this.core.activePlayback, Events.PLAYBACK_BITRATE)
}
bindPlaybackEvents() {
let currentPlayback = this.core.activePlayback
this.listenTo(currentPlayback, Events.PLAYBACK_LEVELS_AVAILABLE, this.fillLevels)
this.listenTo(currentPlayback, Events.PLAYBACK_LEVEL_SWITCH_START, this.startLevelSwitch)
this.listenTo(currentPlayback, Events.PLAYBACK_LEVEL_SWITCH_END, this.stopLevelSwitch)
this.listenTo(currentPlayback, Events.PLAYBACK_BITRATE, this.updateCurrentLevel)
let playbackLevelsAvaialbeWasTriggered = currentPlayback.levels && currentPlayback.levels.length > 0
playbackLevelsAvaialbeWasTriggered && this.fillLevels(currentPlayback.levels)
}
reload() {
this.unBindEvents()
this.bindEvents()
this.bindPlaybackEvents()
}
shouldRender() {
if (!this.core.activeContainer) return false
let currentPlayback = this.core.activePlayback
if (!currentPlayback) return false
let respondsToCurrentLevel = currentPlayback.currentLevel !== undefined
// Only care if we have at least 2 to choose from
let hasLevels = !!(this.levels && this.levels.length > 1)
return respondsToCurrentLevel && hasLevels
}
render() {
if (this.shouldRender()) {
let style = Styler.getStyleFor(pluginStyle, { baseUrl: this.core.options.baseUrl })
this.$el.html(this.template({ 'levels':this.levels, 'title': this.getTitle() }))
this.$el.append(style)
this.core.mediaControl.$('.media-control-right-panel').append(this.el)
this.highlightCurrentLevel()
}
return this
}
fillLevels(levels, initialLevel = AUTO) {
if (this.selectedLevelId === undefined) this.selectedLevelId = initialLevel
this.levels = levels
this.configureLevelsLabels()
this.render()
}
configureLevelsLabels() {
if (this.core.options.levelSelectorConfig === undefined) return
let labelCallback = this.core.options.levelSelectorConfig.labelCallback
if (labelCallback && typeof labelCallback !== 'function')
throw new TypeError('labelCallback must be a function')
let hasLabels = this.core.options.levelSelectorConfig.labels
let labels = hasLabels ? this.core.options.levelSelectorConfig.labels : {}
if (labelCallback || hasLabels) {
let level
let label
for (let levelId in this.levels) {
level = this.levels[levelId]
label = labels[level.id]
if (labelCallback)
level.label = labelCallback(level,label)
else if (label)
level.label = label
}
}
}
findLevelBy(id) {
let foundLevel
this.levels.forEach((level) => { if (level.id === id) foundLevel = level })
return foundLevel
}
onLevelSelect(event) {
this.selectedLevelId = parseInt(event.target.dataset.levelSelectorSelect, 10)
if (this.core.activePlayback.currentLevel == this.selectedLevelId) return false
this.core.activePlayback.currentLevel = this.selectedLevelId
this.toggleContextMenu()
event.stopPropagation()
return false
}
onShowLevelSelectMenu() { this.toggleContextMenu() }
hideSelectLevelMenu() { this.$('.level_selector ul').hide() }
toggleContextMenu() { this.$('.level_selector ul').toggle() }
buttonElement() { return this.$('.level_selector button') }
levelElement(id) { return this.$('.level_selector ul a'+(!isNaN(id) ? '[data-level-selector-select="'+id+'"]' : '')).parent() }
getTitle() { return (this.core.options.levelSelectorConfig || {}).title }
startLevelSwitch() { this.buttonElement().addClass('changing') }
stopLevelSwitch() { this.buttonElement().removeClass('changing') }
updateText(level) {
if (level === AUTO)
this.buttonElement().text(this.currentLevel ? 'AUTO (' + this.currentLevel.label + ')' : 'AUTO')
else
this.buttonElement().text(this.findLevelBy(level).label)
}
updateCurrentLevel(info) {
let level = this.findLevelBy(info.level)
this.currentLevel = level ? level : null
this.highlightCurrentLevel()
}
highlightCurrentLevel() {
this.levelElement().removeClass('current')
this.currentLevel && this.levelElement(this.currentLevel.id).addClass('current')
this.updateText(this.selectedLevelId)
}
}