From a672b0c78a9c699b58a1a58b838b795a468f1538 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 10 Mar 2021 13:40:11 +0100 Subject: [PATCH] better error handling in video decoding --- .../room/timeline/tiles/BaseMediaTile.js | 7 ++- .../web/ui/session/room/timeline/VideoView.js | 53 +++++++++++++++---- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/domain/session/room/timeline/tiles/BaseMediaTile.js b/src/domain/session/room/timeline/tiles/BaseMediaTile.js index ea3812d7..26862902 100644 --- a/src/domain/session/room/timeline/tiles/BaseMediaTile.js +++ b/src/domain/session/room/timeline/tiles/BaseMediaTile.js @@ -106,11 +106,16 @@ export class BaseMediaTile extends MessageTile { get error() { if (this._error) { - return `Could not decrypt media: ${this._error.message}`; + return `Could not load media: ${this._error.message}`; } return null; } + setViewError(err) { + this._error = err; + this.emitChange("error"); + } + async _loadEncryptedFile(file) { const blob = await this._mediaRepository.downloadEncryptedFile(file, true); if (this.isDisposed) { diff --git a/src/platform/web/ui/session/room/timeline/VideoView.js b/src/platform/web/ui/session/room/timeline/VideoView.js index 37a6af1d..340cae6d 100644 --- a/src/platform/web/ui/session/room/timeline/VideoView.js +++ b/src/platform/web/ui/session/room/timeline/VideoView.js @@ -15,10 +15,11 @@ limitations under the License. */ import {BaseMediaView} from "./BaseMediaView.js"; +import {domEventAsPromise} from "../../../../dom/utils.js"; export class VideoView extends BaseMediaView { - renderMedia(t, vm) { - return t.video({ + renderMedia(t) { + const video = t.video({ // provide empty data url if video is not decrypted yet. // Chrome/Electron need this to enable the play button. src: vm => vm.videoUrl || `data:${vm.mimeType},`, @@ -26,13 +27,47 @@ export class VideoView extends BaseMediaView { controls: true, preload: "none", poster: vm => vm.thumbnailUrl, - onPlay: async evt => { - if (!vm.videoUrl) { - await vm.loadVideo(); - evt.target.play(); - } - }, - style: `max-width: ${vm.width}px; max-height: ${vm.height}px;` + onPlay: this._onPlay.bind(this), + style: vm => `max-width: ${vm.width}px; max-height: ${vm.height}px;${vm.isPending ? "z-index: -1": ""}` }); + + video.addEventListener("error", this._onError.bind(this)); + + return video; + } + + async _onPlay(evt) { + const vm = this.value; + // download and decrypt the video if needed, + if (!vm.videoUrl) { + try { + const video = evt.target; + // this will trigger the src to update + await vm.loadVideo(); + // important to only listen for this after src has changed, + // or we get the error for the placeholder data url + const loadPromise = domEventAsPromise(video, "loadeddata"); + // now, reload the video and play + video.load(); + await loadPromise; + video.play(); + } catch (err) {/* errors are already caught in error event handler */} + } + } + + _onError(evt) { + const vm = this.value; + const video = evt.target; + const err = video.error; + if (err instanceof window.MediaError && err.code === 4) { + if (!video.src.startsWith("data:")) { + vm.setViewError(new Error(`this browser does not support videos of type ${vm.mimeType}.`)); + } else { + // ignore placeholder url failing to load + return; + } + } else { + vm.setViewError(err); + } } }