From 6890beb95a0b06f936dbac61c5bc2024d12a0d2b Mon Sep 17 00:00:00 2001 From: Blake Erickson Date: Mon, 24 Apr 2023 12:34:30 -0600 Subject: [PATCH] FIX: Blank video thumbnails (#21200) * FIX: Blank video thumbnails On some mobile and possibly other browsers, the automatic video thumbnail generation would create blank or all white images. This commit addresses several different issues that was preventing image generation from working correctly on mobile. * fix typo --- .../mixins/composer-video-thumbnail-uppy.js | 122 ++++++++++-------- 1 file changed, 66 insertions(+), 56 deletions(-) diff --git a/app/assets/javascripts/discourse/app/mixins/composer-video-thumbnail-uppy.js b/app/assets/javascripts/discourse/app/mixins/composer-video-thumbnail-uppy.js index b94f465f0a1..9346dfff85e 100644 --- a/app/assets/javascripts/discourse/app/mixins/composer-video-thumbnail-uppy.js +++ b/app/assets/javascripts/discourse/app/mixins/composer-video-thumbnail-uppy.js @@ -21,76 +21,86 @@ export default Mixin.create(ExtendableUploader, UppyS3Multipart, { let video = document.createElement("video"); video.src = URL.createObjectURL(videoFile.data); + // These attributes are needed for thumbnail generation on mobile. + // This video tag is not visible, so this is all happening in the background. + video.autoplay = true; + video.muted = true; + video.playsinline = true; + let videoSha1 = uploadUrl .substring(uploadUrl.lastIndexOf("/") + 1) .split(".")[0]; - // Wait for the video element to load, otherwise the canvas will be empty - video.oncanplay = () => { + // Wait for the video element to load, otherwise the canvas will be empty. + // iOS Safari prefers onloadedmetadata over oncanplay. + video.onloadedmetadata = () => { let canvas = document.createElement("canvas"); let ctx = canvas.getContext("2d"); - let videoHeight, videoWidth; - videoHeight = video.videoHeight; - videoWidth = video.videoWidth; - canvas.width = videoWidth; - canvas.height = videoHeight; + canvas.width = video.videoWidth; + canvas.height = video.videoHeight; - ctx.drawImage(video, 0, 0, videoWidth, videoHeight); + // A timeout is needed on mobile. + setTimeout(() => { + ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight); + }, 100); - // upload video thumbnail - canvas.toBlob((blob) => { - this._uppyInstance = new Uppy({ - id: "video-thumbnail", - meta: { - upload_type: `thumbnail`, - videoSha1, - }, - autoProceed: true, - }); + // A timeout is needed on mobile. + setTimeout(() => { + // upload video thumbnail + canvas.toBlob((blob) => { + this._uppyInstance = new Uppy({ + id: "video-thumbnail", + meta: { + upload_type: `thumbnail`, + videoSha1, + }, + autoProceed: true, + }); - if (this.siteSettings.enable_upload_debug_mode) { - this._instrumentUploadTimings(); - } - - if (this.siteSettings.enable_direct_s3_uploads) { - this._useS3MultipartUploads(); - } else { - this._useXHRUploads(); - } - this._uppyInstance.use(DropTarget, { target: this.element }); - - this._uppyInstance.on("upload", () => { - this.set("uploading", true); - }); - - this._uppyInstance.on("upload-success", () => { - this.set("uploading", false); - }); - - this._uppyInstance.on("upload-error", (file, error, response) => { - let message = I18n.t("wizard.upload_error"); - if (response.body.errors) { - message = response.body.errors.join("\n"); + if (this.siteSettings.enable_upload_debug_mode) { + this._instrumentUploadTimings(); } - // eslint-disable-next-line no-console - console.error(message); - this.set("uploading", false); - }); + if (this.siteSettings.enable_direct_s3_uploads) { + this._useS3MultipartUploads(); + } else { + this._useXHRUploads(); + } + this._uppyInstance.use(DropTarget, { target: this.element }); - try { - this._uppyInstance.addFile({ - source: `${this.id} thumbnail`, - name: `${videoSha1}`, - type: blob.type, - data: blob, + this._uppyInstance.on("upload", () => { + this.set("uploading", true); }); - } catch (err) { - warn(`error adding files to uppy: ${err}`, { - id: "discourse.upload.uppy-add-files-error", + + this._uppyInstance.on("upload-success", () => { + this.set("uploading", false); }); - } - }); + + this._uppyInstance.on("upload-error", (file, error, response) => { + let message = I18n.t("wizard.upload_error"); + if (response.body.errors) { + message = response.body.errors.join("\n"); + } + + // eslint-disable-next-line no-console + console.error(message); + this.set("uploading", false); + }); + + try { + this._uppyInstance.addFile({ + source: `${this.id} thumbnail`, + name: `${videoSha1}`, + type: blob.type, + data: blob, + }); + } catch (err) { + warn(`error adding files to uppy: ${err}`, { + id: "discourse.upload.uppy-add-files-error", + }); + } + }); + }, 100); }; },