saveAverageBandwidth(data.bandwidthEstimate)
})
+ const offlineNotificationElem = document.createElement('div')
+ offlineNotificationElem.classList.add('vjs-peertube-offline-notification')
+ offlineNotificationElem.innerText = player.localize('You seem to be offline and the video may not work')
+
+ const handleOnline = () => {
+ player.el().removeChild(offlineNotificationElem)
+ logger.info('The browser is online')
+ }
+
+ const handleOffline = () => {
+ player.el().appendChild(offlineNotificationElem)
+ logger.info('The browser is offline')
+ }
+
+ window.addEventListener('online', handleOnline)
+ window.addEventListener('offline', handleOffline)
+
+ player.on('dispose', () => {
+ window.removeEventListener('online', handleOnline)
+ window.removeEventListener('offline', handleOffline)
+ })
+
return res(player)
})
})
}
}
+ private _getHumanErrorMsg (error: { message: string, code?: number }) {
+ switch (error.code) {
+ default:
+ return error.message
+ }
+ }
+
+ private _handleUnrecovarableError (error: any) {
+ if (this.hls.levels.filter(l => l.id > -1).length > 1) {
+ this._removeQuality(this.hls.loadLevel)
+ return
+ }
+
+ this.hls.destroy()
+ logger.info('bubbling error up to VIDEOJS')
+ this.tech.error = () => ({
+ ...error,
+ message: this._getHumanErrorMsg(error)
+ })
+ this.tech.trigger('error')
+ }
+
private _handleMediaError (error: any) {
if (this.errorCounts[Hlsjs.ErrorTypes.MEDIA_ERROR] === 1) {
logger.info('trying to recover media error')
}
if (this.errorCounts[Hlsjs.ErrorTypes.MEDIA_ERROR] > 2) {
- logger.info('bubbling media error up to VIDEOJS')
- this.hls.destroy()
- this.tech.error = () => error
- this.tech.trigger('error')
+ this._handleUnrecovarableError(error)
}
}
private _handleNetworkError (error: any) {
+ if (navigator.onLine === false) return
+
if (this.errorCounts[Hlsjs.ErrorTypes.NETWORK_ERROR] <= this.maxNetworkErrorRecovery) {
logger.info('trying to recover network error')
return
}
- logger.info('bubbling network error up to VIDEOJS')
- this.hls.destroy()
- this.tech.error = () => error
- this.tech.trigger('error')
+ this._handleUnrecovarableError(error)
}
private _onError (_event: any, data: ErrorData) {
error.code = 3
this._handleMediaError(error)
} else if (data.fatal) {
- this.hls.destroy()
- logger.info('bubbling error up to VIDEOJS')
- this.tech.error = () => error as any
- this.tech.trigger('error')
+ this._handleUnrecovarableError(error)
}
}
return '0'
}
+ private _removeQuality (index: number) {
+ this.hls.removeLevel(index)
+ this.player.peertubeResolutions().remove(index)
+ this.hls.currentLevel = -1
+ }
+
private _notifyVideoQualities () {
if (!this.metadata) return
this.p2pEngine = this.options.loader.getEngine()
this.p2pEngine.on(Events.SegmentError, (segment: Segment, err) => {
+ if (navigator.onLine === false) return
+
logger.error(`Segment ${segment.id} error.`, err)
this.options.redundancyUrlManager.removeBySegmentUrl(segment.requestUrl)
}
displayFatalError () {
+ this.player.loadingSpinner.hide()
+
+ const buildModal = (error: MediaError) => {
+ const localize = this.player.localize.bind(this.player)
+
+ const wrapper = document.createElement('div')
+ const header = document.createElement('h1')
+ header.innerText = localize('Failed to play video')
+ wrapper.appendChild(header)
+ const desc = document.createElement('div')
+ desc.innerText = localize('The video failed to play due to technical issues.')
+ wrapper.appendChild(desc)
+ const details = document.createElement('p')
+ details.classList.add('error-details')
+ details.innerText = error.message
+ wrapper.appendChild(details)
+
+ return wrapper
+ }
+
+ const modal = this.player.createModal(buildModal(this.player.error()), {
+ temporary: false,
+ uncloseable: true
+ })
+ modal.addClass('vjs-custom-error-display')
+
this.player.addClass('vjs-error-display-enabled')
}
this.trigger('resolutionsAdded')
}
+ remove (resolutionIndex: number) {
+ this.resolutions = this.resolutions.filter(r => r.id !== resolutionIndex)
+ this.trigger('resolutionRemoved')
+ }
+
getResolutions () {
return this.resolutions
}
this.controlText('Quality')
player.peertubeResolutions().on('resolutionsAdded', () => this.buildQualities())
+ player.peertubeResolutions().on('resolutionRemoved', () => this.cleanupQualities())
// For parent
player.peertubeResolutions().on('resolutionChanged', () => {
this.trigger('menuChanged')
}
+
+ private cleanupQualities () {
+ const resolutions = this.player().peertubeResolutions().getResolutions()
+
+ this.menu.children().forEach((children: ResolutionMenuItem) => {
+ if (children.resolutionId === undefined) {
+ return
+ }
+
+ if (resolutions.find(r => r.id === children.resolutionId)) {
+ return
+ }
+
+ this.menu.removeChild(children)
+ })
+
+ this.trigger('menuChanged')
+ }
}
videojs.registerComponent('ResolutionMenuButton', ResolutionMenuButton)
}
class ResolutionMenuItem extends MenuItem {
- private readonly resolutionId: number
+ readonly resolutionId: number
private readonly label: string
private autoResolutionEnabled: boolean
@use './bezels';
@use './playlist';
@use './stats';
+@use './offline-notification';
--- /dev/null
+$height: 40px;
+
+.vjs-peertube-offline-notification {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: $height;
+ color: #000;
+ background-color: var(--mainColorLightest);
+ text-align: center;
+ z-index: 1;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.vjs-modal-dialog
+.vjs-modal-dialog-content,
+.video-js .vjs-modal-dialog {
+ top: $height;
+}
}
}
+.vjs-error-display {
+ display: none;
+}
+
+.vjs-custom-error-display {
+ font-family: $main-fonts;
+
+ .error-details {
+ margin-top: 40px;
+ font-size: 80%;
+ }
+}
+
// Error display disabled
.vjs-error:not(.vjs-error-display-enabled) {
- .vjs-error-display {
+ .vjs-custom-error-display {
display: none;
}
// Error display enabled
.vjs-error.vjs-error-display-enabled {
- .vjs-error-display {
+ .vjs-custom-error-display {
display: block;
}
}