aboutsummaryrefslogtreecommitdiffhomepage
path: root/client
diff options
context:
space:
mode:
authorkontrollanten <6680299+kontrollanten@users.noreply.github.com>2022-09-28 11:52:23 +0200
committerGitHub <noreply@github.com>2022-09-28 11:52:23 +0200
commitf2a16d93b476aff16d5353e4d44350298ec7e01c (patch)
tree36c43eb3299c4a1137ca38dd1a564701a5a27236 /client
parent43972ee466740e91b16c08fe106551657969e669 (diff)
downloadPeerTube-f2a16d93b476aff16d5353e4d44350298ec7e01c.tar.gz
PeerTube-f2a16d93b476aff16d5353e4d44350298ec7e01c.tar.zst
PeerTube-f2a16d93b476aff16d5353e4d44350298ec7e01c.zip
Handle network issues in video player (#5138)
* feat(client/player): handle network offline * feat(client/player): human friendly err msg * feat(client/player): handle broken resolutions When an error occurs for a resolution, remove the resolution and try with another resolution. * fix(client/player): prevent err handl when offline * fix(client/player): localize offline text
Diffstat (limited to 'client')
-rw-r--r--client/src/assets/player/peertube-player-manager.ts22
-rw-r--r--client/src/assets/player/shared/p2p-media-loader/hls-plugin.ts45
-rw-r--r--client/src/assets/player/shared/p2p-media-loader/p2p-media-loader-plugin.ts2
-rw-r--r--client/src/assets/player/shared/peertube/peertube-plugin.ts26
-rw-r--r--client/src/assets/player/shared/resolutions/peertube-resolutions-plugin.ts5
-rw-r--r--client/src/assets/player/shared/settings/resolution-menu-button.ts19
-rw-r--r--client/src/assets/player/shared/settings/resolution-menu-item.ts2
-rw-r--r--client/src/sass/player/index.scss1
-rw-r--r--client/src/sass/player/offline-notification.scss22
-rw-r--r--client/src/sass/player/peertube-skin.scss17
10 files changed, 146 insertions, 15 deletions
diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts
index 0d4acc3d9..533ee1bb8 100644
--- a/client/src/assets/player/peertube-player-manager.ts
+++ b/client/src/assets/player/peertube-player-manager.ts
@@ -129,6 +129,28 @@ export class PeertubePlayerManager {
129 saveAverageBandwidth(data.bandwidthEstimate) 129 saveAverageBandwidth(data.bandwidthEstimate)
130 }) 130 })
131 131
132 const offlineNotificationElem = document.createElement('div')
133 offlineNotificationElem.classList.add('vjs-peertube-offline-notification')
134 offlineNotificationElem.innerText = player.localize('You seem to be offline and the video may not work')
135
136 const handleOnline = () => {
137 player.el().removeChild(offlineNotificationElem)
138 logger.info('The browser is online')
139 }
140
141 const handleOffline = () => {
142 player.el().appendChild(offlineNotificationElem)
143 logger.info('The browser is offline')
144 }
145
146 window.addEventListener('online', handleOnline)
147 window.addEventListener('offline', handleOffline)
148
149 player.on('dispose', () => {
150 window.removeEventListener('online', handleOnline)
151 window.removeEventListener('offline', handleOffline)
152 })
153
132 return res(player) 154 return res(player)
133 }) 155 })
134 }) 156 })
diff --git a/client/src/assets/player/shared/p2p-media-loader/hls-plugin.ts b/client/src/assets/player/shared/p2p-media-loader/hls-plugin.ts
index e49e5c694..a14beb347 100644
--- a/client/src/assets/player/shared/p2p-media-loader/hls-plugin.ts
+++ b/client/src/assets/player/shared/p2p-media-loader/hls-plugin.ts
@@ -211,6 +211,28 @@ class Html5Hlsjs {
211 } 211 }
212 } 212 }
213 213
214 private _getHumanErrorMsg (error: { message: string, code?: number }) {
215 switch (error.code) {
216 default:
217 return error.message
218 }
219 }
220
221 private _handleUnrecovarableError (error: any) {
222 if (this.hls.levels.filter(l => l.id > -1).length > 1) {
223 this._removeQuality(this.hls.loadLevel)
224 return
225 }
226
227 this.hls.destroy()
228 logger.info('bubbling error up to VIDEOJS')
229 this.tech.error = () => ({
230 ...error,
231 message: this._getHumanErrorMsg(error)
232 })
233 this.tech.trigger('error')
234 }
235
214 private _handleMediaError (error: any) { 236 private _handleMediaError (error: any) {
215 if (this.errorCounts[Hlsjs.ErrorTypes.MEDIA_ERROR] === 1) { 237 if (this.errorCounts[Hlsjs.ErrorTypes.MEDIA_ERROR] === 1) {
216 logger.info('trying to recover media error') 238 logger.info('trying to recover media error')
@@ -226,14 +248,13 @@ class Html5Hlsjs {
226 } 248 }
227 249
228 if (this.errorCounts[Hlsjs.ErrorTypes.MEDIA_ERROR] > 2) { 250 if (this.errorCounts[Hlsjs.ErrorTypes.MEDIA_ERROR] > 2) {
229 logger.info('bubbling media error up to VIDEOJS') 251 this._handleUnrecovarableError(error)
230 this.hls.destroy()
231 this.tech.error = () => error
232 this.tech.trigger('error')
233 } 252 }
234 } 253 }
235 254
236 private _handleNetworkError (error: any) { 255 private _handleNetworkError (error: any) {
256 if (navigator.onLine === false) return
257
237 if (this.errorCounts[Hlsjs.ErrorTypes.NETWORK_ERROR] <= this.maxNetworkErrorRecovery) { 258 if (this.errorCounts[Hlsjs.ErrorTypes.NETWORK_ERROR] <= this.maxNetworkErrorRecovery) {
238 logger.info('trying to recover network error') 259 logger.info('trying to recover network error')
239 260
@@ -248,10 +269,7 @@ class Html5Hlsjs {
248 return 269 return
249 } 270 }
250 271
251 logger.info('bubbling network error up to VIDEOJS') 272 this._handleUnrecovarableError(error)
252 this.hls.destroy()
253 this.tech.error = () => error
254 this.tech.trigger('error')
255 } 273 }
256 274
257 private _onError (_event: any, data: ErrorData) { 275 private _onError (_event: any, data: ErrorData) {
@@ -273,10 +291,7 @@ class Html5Hlsjs {
273 error.code = 3 291 error.code = 3
274 this._handleMediaError(error) 292 this._handleMediaError(error)
275 } else if (data.fatal) { 293 } else if (data.fatal) {
276 this.hls.destroy() 294 this._handleUnrecovarableError(error)
277 logger.info('bubbling error up to VIDEOJS')
278 this.tech.error = () => error as any
279 this.tech.trigger('error')
280 } 295 }
281 } 296 }
282 297
@@ -292,6 +307,12 @@ class Html5Hlsjs {
292 return '0' 307 return '0'
293 } 308 }
294 309
310 private _removeQuality (index: number) {
311 this.hls.removeLevel(index)
312 this.player.peertubeResolutions().remove(index)
313 this.hls.currentLevel = -1
314 }
315
295 private _notifyVideoQualities () { 316 private _notifyVideoQualities () {
296 if (!this.metadata) return 317 if (!this.metadata) return
297 318
diff --git a/client/src/assets/player/shared/p2p-media-loader/p2p-media-loader-plugin.ts b/client/src/assets/player/shared/p2p-media-loader/p2p-media-loader-plugin.ts
index 56068e340..3c4482f2e 100644
--- a/client/src/assets/player/shared/p2p-media-loader/p2p-media-loader-plugin.ts
+++ b/client/src/assets/player/shared/p2p-media-loader/p2p-media-loader-plugin.ts
@@ -115,6 +115,8 @@ class P2pMediaLoaderPlugin extends Plugin {
115 this.p2pEngine = this.options.loader.getEngine() 115 this.p2pEngine = this.options.loader.getEngine()
116 116
117 this.p2pEngine.on(Events.SegmentError, (segment: Segment, err) => { 117 this.p2pEngine.on(Events.SegmentError, (segment: Segment, err) => {
118 if (navigator.onLine === false) return
119
118 logger.error(`Segment ${segment.id} error.`, err) 120 logger.error(`Segment ${segment.id} error.`, err)
119 121
120 this.options.redundancyUrlManager.removeBySegmentUrl(segment.requestUrl) 122 this.options.redundancyUrlManager.removeBySegmentUrl(segment.requestUrl)
diff --git a/client/src/assets/player/shared/peertube/peertube-plugin.ts b/client/src/assets/player/shared/peertube/peertube-plugin.ts
index 83c32415e..a5d712d70 100644
--- a/client/src/assets/player/shared/peertube/peertube-plugin.ts
+++ b/client/src/assets/player/shared/peertube/peertube-plugin.ts
@@ -125,6 +125,32 @@ class PeerTubePlugin extends Plugin {
125 } 125 }
126 126
127 displayFatalError () { 127 displayFatalError () {
128 this.player.loadingSpinner.hide()
129
130 const buildModal = (error: MediaError) => {
131 const localize = this.player.localize.bind(this.player)
132
133 const wrapper = document.createElement('div')
134 const header = document.createElement('h1')
135 header.innerText = localize('Failed to play video')
136 wrapper.appendChild(header)
137 const desc = document.createElement('div')
138 desc.innerText = localize('The video failed to play due to technical issues.')
139 wrapper.appendChild(desc)
140 const details = document.createElement('p')
141 details.classList.add('error-details')
142 details.innerText = error.message
143 wrapper.appendChild(details)
144
145 return wrapper
146 }
147
148 const modal = this.player.createModal(buildModal(this.player.error()), {
149 temporary: false,
150 uncloseable: true
151 })
152 modal.addClass('vjs-custom-error-display')
153
128 this.player.addClass('vjs-error-display-enabled') 154 this.player.addClass('vjs-error-display-enabled')
129 } 155 }
130 156
diff --git a/client/src/assets/player/shared/resolutions/peertube-resolutions-plugin.ts b/client/src/assets/player/shared/resolutions/peertube-resolutions-plugin.ts
index e7899ac71..4fafd27b1 100644
--- a/client/src/assets/player/shared/resolutions/peertube-resolutions-plugin.ts
+++ b/client/src/assets/player/shared/resolutions/peertube-resolutions-plugin.ts
@@ -21,6 +21,11 @@ class PeerTubeResolutionsPlugin extends Plugin {
21 this.trigger('resolutionsAdded') 21 this.trigger('resolutionsAdded')
22 } 22 }
23 23
24 remove (resolutionIndex: number) {
25 this.resolutions = this.resolutions.filter(r => r.id !== resolutionIndex)
26 this.trigger('resolutionRemoved')
27 }
28
24 getResolutions () { 29 getResolutions () {
25 return this.resolutions 30 return this.resolutions
26 } 31 }
diff --git a/client/src/assets/player/shared/settings/resolution-menu-button.ts b/client/src/assets/player/shared/settings/resolution-menu-button.ts
index a0b349f67..672411c11 100644
--- a/client/src/assets/player/shared/settings/resolution-menu-button.ts
+++ b/client/src/assets/player/shared/settings/resolution-menu-button.ts
@@ -12,6 +12,7 @@ class ResolutionMenuButton extends MenuButton {
12 this.controlText('Quality') 12 this.controlText('Quality')
13 13
14 player.peertubeResolutions().on('resolutionsAdded', () => this.buildQualities()) 14 player.peertubeResolutions().on('resolutionsAdded', () => this.buildQualities())
15 player.peertubeResolutions().on('resolutionRemoved', () => this.cleanupQualities())
15 16
16 // For parent 17 // For parent
17 player.peertubeResolutions().on('resolutionChanged', () => { 18 player.peertubeResolutions().on('resolutionChanged', () => {
@@ -82,6 +83,24 @@ class ResolutionMenuButton extends MenuButton {
82 83
83 this.trigger('menuChanged') 84 this.trigger('menuChanged')
84 } 85 }
86
87 private cleanupQualities () {
88 const resolutions = this.player().peertubeResolutions().getResolutions()
89
90 this.menu.children().forEach((children: ResolutionMenuItem) => {
91 if (children.resolutionId === undefined) {
92 return
93 }
94
95 if (resolutions.find(r => r.id === children.resolutionId)) {
96 return
97 }
98
99 this.menu.removeChild(children)
100 })
101
102 this.trigger('menuChanged')
103 }
85} 104}
86 105
87videojs.registerComponent('ResolutionMenuButton', ResolutionMenuButton) 106videojs.registerComponent('ResolutionMenuButton', ResolutionMenuButton)
diff --git a/client/src/assets/player/shared/settings/resolution-menu-item.ts b/client/src/assets/player/shared/settings/resolution-menu-item.ts
index 678eb368b..c59b8b891 100644
--- a/client/src/assets/player/shared/settings/resolution-menu-item.ts
+++ b/client/src/assets/player/shared/settings/resolution-menu-item.ts
@@ -7,7 +7,7 @@ export interface ResolutionMenuItemOptions extends videojs.MenuItemOptions {
7} 7}
8 8
9class ResolutionMenuItem extends MenuItem { 9class ResolutionMenuItem extends MenuItem {
10 private readonly resolutionId: number 10 readonly resolutionId: number
11 private readonly label: string 11 private readonly label: string
12 12
13 private autoResolutionEnabled: boolean 13 private autoResolutionEnabled: boolean
diff --git a/client/src/sass/player/index.scss b/client/src/sass/player/index.scss
index 7420460e7..5d0307d95 100644
--- a/client/src/sass/player/index.scss
+++ b/client/src/sass/player/index.scss
@@ -9,3 +9,4 @@
9@use './bezels'; 9@use './bezels';
10@use './playlist'; 10@use './playlist';
11@use './stats'; 11@use './stats';
12@use './offline-notification';
diff --git a/client/src/sass/player/offline-notification.scss b/client/src/sass/player/offline-notification.scss
new file mode 100644
index 000000000..2108c2e30
--- /dev/null
+++ b/client/src/sass/player/offline-notification.scss
@@ -0,0 +1,22 @@
1$height: 40px;
2
3.vjs-peertube-offline-notification {
4 position: absolute;
5 top: 0;
6 left: 0;
7 right: 0;
8 height: $height;
9 color: #000;
10 background-color: var(--mainColorLightest);
11 text-align: center;
12 z-index: 1;
13 display: flex;
14 justify-content: center;
15 align-items: center;
16}
17
18.vjs-modal-dialog
19.vjs-modal-dialog-content,
20.video-js .vjs-modal-dialog {
21 top: $height;
22}
diff --git a/client/src/sass/player/peertube-skin.scss b/client/src/sass/player/peertube-skin.scss
index 43c144624..d4c43ff68 100644
--- a/client/src/sass/player/peertube-skin.scss
+++ b/client/src/sass/player/peertube-skin.scss
@@ -189,9 +189,22 @@ body {
189 } 189 }
190} 190}
191 191
192.vjs-error-display {
193 display: none;
194}
195
196.vjs-custom-error-display {
197 font-family: $main-fonts;
198
199 .error-details {
200 margin-top: 40px;
201 font-size: 80%;
202 }
203}
204
192// Error display disabled 205// Error display disabled
193.vjs-error:not(.vjs-error-display-enabled) { 206.vjs-error:not(.vjs-error-display-enabled) {
194 .vjs-error-display { 207 .vjs-custom-error-display {
195 display: none; 208 display: none;
196 } 209 }
197 210
@@ -202,7 +215,7 @@ body {
202 215
203// Error display enabled 216// Error display enabled
204.vjs-error.vjs-error-display-enabled { 217.vjs-error.vjs-error-display-enabled {
205 .vjs-error-display { 218 .vjs-custom-error-display {
206 display: block; 219 display: block;
207 } 220 }
208} 221}