From 79a05bbcbff2be6a65113fd19c433bb3c5d5da4f Mon Sep 17 00:00:00 2001 From: "Abdelrahman Khattab (via MelvinBot)" Date: Tue, 10 Mar 2026 20:28:01 +0000 Subject: [PATCH 1/4] Fix: Catch AbortError from video.play() on web to prevent console errors during rapid seeking Co-authored-by: Abdelrahman Khattab --- patches/expo-video/details.md | 12 +++++ ...eo+3.0.12+002+catch_play_abort_error.patch | 50 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 patches/expo-video/expo-video+3.0.12+002+catch_play_abort_error.patch diff --git a/patches/expo-video/details.md b/patches/expo-video/details.md index ede6026ad2a9..ebe2896f0f1b 100644 --- a/patches/expo-video/details.md +++ b/patches/expo-video/details.md @@ -11,3 +11,15 @@ - Upstream PR/issue: https://github.com/expo/expo/issues/40743 - E/App issue: 🛑 - PR Introducing Patch: https://github.com/Expensify/App/pull/66793 + +### [expo-video+3.0.12+002+catch_play_abort_error.patch](expo-video+3.0.12+002+catch_play_abort_error.patch) + +- Reason: + + ``` + Wraps all 5 HTMLVideoElement.play() calls in VideoPlayer.web.js with .catch() to silently swallow AbortError. When users rapidly seek the video progress bar, pause() can interrupt a pending play() Promise, causing an unhandled AbortError rejection in the console. This is the standard fix recommended by Chrome's documentation (https://developer.chrome.com/blog/play-request-was-interrupted). + ``` + +- Upstream PR/issue: N/A +- E/App issue: https://github.com/Expensify/App/issues/84294 +- PR Introducing Patch: TBD diff --git a/patches/expo-video/expo-video+3.0.12+002+catch_play_abort_error.patch b/patches/expo-video/expo-video+3.0.12+002+catch_play_abort_error.patch new file mode 100644 index 000000000000..dcda750bb4d8 --- /dev/null +++ b/patches/expo-video/expo-video+3.0.12+002+catch_play_abort_error.patch @@ -0,0 +1,50 @@ + +diff --git a/node_modules/expo-video/build/VideoPlayer.web.js b/node_modules/expo-video/build/VideoPlayer.web.js +index 16df18b..a1b2c3d 100644 +--- a/node_modules/expo-video/build/VideoPlayer.web.js ++++ b/node_modules/expo-video/build/VideoPlayer.web.js +@@ -222,7 +222,7 @@ export default class VideoPlayerWeb extends globalThis.expo.SharedObject { + } + play() { + this._mountedVideos.forEach((video) => { +- video.play(); ++ video.play()?.catch((e) => { if (e.name !== 'AbortError') throw e; }); + }); + } + pause() { +@@ -237,7 +237,7 @@ export default class VideoPlayerWeb extends globalThis.expo.SharedObject { + if (uri) { + video.setAttribute('src', uri); + video.load(); +- video.play(); ++ video.play()?.catch((e) => { if (e.name !== 'AbortError') throw e; }); + } + else { + video.removeAttribute('src'); +@@ -262,7 +262,7 @@ export default class VideoPlayerWeb extends globalThis.expo.SharedObject { + replay() { + this._mountedVideos.forEach((video) => { + video.currentTime = 0; +- video.play(); ++ video.play()?.catch((e) => { if (e.name !== 'AbortError') throw e; }); + }); + this.playing = true; + } +@@ -277,7 +277,7 @@ export default class VideoPlayerWeb extends globalThis.expo.SharedObject { + video.pause(); + } + else { +- video.play(); ++ video.play()?.catch((e) => { if (e.name !== 'AbortError') throw e; }); + } + video.currentTime = firstVideo.currentTime; + video.volume = firstVideo.volume; +@@ -302,7 +302,7 @@ export default class VideoPlayerWeb extends globalThis.expo.SharedObject { + }); + this.playing = true; + this._mountedVideos.forEach((mountedVideo) => { +- mountedVideo.play(); ++ mountedVideo.play()?.catch((e) => { if (e.name !== 'AbortError') throw e; }); + }); + }; + video.onpause = (e) => { From 5f94c478a3149fb95ef83f1586d48bee8476241a Mon Sep 17 00:00:00 2001 From: "Abdelrahman Khattab (via MelvinBot)" Date: Thu, 19 Mar 2026 19:19:00 +0000 Subject: [PATCH 2/4] Update expo-video patch for version 55.0.3 The expo-video package was upgraded from 3.0.12 to 55.0.3 on main, removing the old patches directory. This recreates the AbortError catch patch for the new version, adapting to a minor code change in the onplay handler that now includes an if-guard. Co-authored-by: Abdelrahman Khattab --- patches/expo-video/details.md | 5 ++ ...eo+55.0.3+001+catch_play_abort_error.patch | 49 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 patches/expo-video/details.md create mode 100644 patches/expo-video/expo-video+55.0.3+001+catch_play_abort_error.patch diff --git a/patches/expo-video/details.md b/patches/expo-video/details.md new file mode 100644 index 000000000000..d6d5a5d5a40f --- /dev/null +++ b/patches/expo-video/details.md @@ -0,0 +1,5 @@ +# `expo-video` patches + +### [expo-video+55.0.3+001+catch_play_abort_error.patch](expo-video+55.0.3+001+catch_play_abort_error.patch) + +- Reason: When rapidly seeking a video via the progress bar on web, `HTMLVideoElement.play()` returns a Promise that gets rejected with `AbortError` if `pause()` is called before it resolves. This patch wraps all 5 `video.play()` call sites in `VideoPlayer.web.js` with `.catch()` to silently swallow `AbortError` while re-throwing any other errors. This is the [standard fix recommended by Chrome](https://developer.chrome.com/blog/play-request-was-interrupted). diff --git a/patches/expo-video/expo-video+55.0.3+001+catch_play_abort_error.patch b/patches/expo-video/expo-video+55.0.3+001+catch_play_abort_error.patch new file mode 100644 index 000000000000..b6983ec7c5f3 --- /dev/null +++ b/patches/expo-video/expo-video+55.0.3+001+catch_play_abort_error.patch @@ -0,0 +1,49 @@ + +diff --git a/node_modules/expo-video/build/VideoPlayer.web.js b/node_modules/expo-video/build/VideoPlayer.web.js +index 16df18b..a1b2c3d 100644 +--- a/node_modules/expo-video/build/VideoPlayer.web.js ++++ b/node_modules/expo-video/build/VideoPlayer.web.js +@@ -224,7 +224,7 @@ export default class VideoPlayerWeb extends globalThis.expo.SharedObject { + } + play() { + this._mountedVideos.forEach((video) => { +- video.play(); ++ video.play()?.catch((e) => { if (e.name !== 'AbortError') throw e; }); + }); + } + pause() { +@@ -239,7 +239,7 @@ export default class VideoPlayerWeb extends globalThis.expo.SharedObject { + if (uri) { + video.setAttribute('src', uri); + video.load(); +- video.play(); ++ video.play()?.catch((e) => { if (e.name !== 'AbortError') throw e; }); + } + else { + video.removeAttribute('src'); +@@ -264,7 +264,7 @@ export default class VideoPlayerWeb extends globalThis.expo.SharedObject { + replay() { + this._mountedVideos.forEach((video) => { + video.currentTime = 0; +- video.play(); ++ video.play()?.catch((e) => { if (e.name !== 'AbortError') throw e; }); + }); + this.playing = true; + } +@@ -279,7 +279,7 @@ export default class VideoPlayerWeb extends globalThis.expo.SharedObject { + video.pause(); + } + else { +- video.play(); ++ video.play()?.catch((e) => { if (e.name !== 'AbortError') throw e; }); + } + video.currentTime = firstVideo.currentTime; + video.volume = firstVideo.volume; +@@ -305,7 +305,7 @@ export default class VideoPlayerWeb extends globalThis.expo.SharedObject { + this._mountedVideos.forEach((mountedVideo) => { + if (e.target !== mountedVideo) { +- mountedVideo.play(); ++ mountedVideo.play()?.catch((e) => { if (e.name !== 'AbortError') throw e; }); + } + }); + }; From e32c002b0bacd454a9bbedef3b2943d224d980d8 Mon Sep 17 00:00:00 2001 From: "Abdelrahman Khattab (via MelvinBot)" Date: Thu, 19 Mar 2026 19:26:38 +0000 Subject: [PATCH 3/4] Fix: correct patch file format for expo-video Remove empty first line and fix last hunk line count (7 -> 6) so patch-package can parse the file. Co-authored-by: Abdelrahman Khattab --- .../expo-video+55.0.3+001+catch_play_abort_error.patch | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/patches/expo-video/expo-video+55.0.3+001+catch_play_abort_error.patch b/patches/expo-video/expo-video+55.0.3+001+catch_play_abort_error.patch index b6983ec7c5f3..c4587df765ef 100644 --- a/patches/expo-video/expo-video+55.0.3+001+catch_play_abort_error.patch +++ b/patches/expo-video/expo-video+55.0.3+001+catch_play_abort_error.patch @@ -1,4 +1,3 @@ - diff --git a/node_modules/expo-video/build/VideoPlayer.web.js b/node_modules/expo-video/build/VideoPlayer.web.js index 16df18b..a1b2c3d 100644 --- a/node_modules/expo-video/build/VideoPlayer.web.js @@ -39,7 +38,7 @@ index 16df18b..a1b2c3d 100644 } video.currentTime = firstVideo.currentTime; video.volume = firstVideo.volume; -@@ -305,7 +305,7 @@ export default class VideoPlayerWeb extends globalThis.expo.SharedObject { +@@ -305,6 +305,6 @@ export default class VideoPlayerWeb extends globalThis.expo.SharedObject { this._mountedVideos.forEach((mountedVideo) => { if (e.target !== mountedVideo) { - mountedVideo.play(); From c91142dc02ad454cf4f6012e66da215dcf4c3e78 Mon Sep 17 00:00:00 2001 From: "Abdelrahman Khattab (via MelvinBot)" Date: Thu, 19 Mar 2026 19:36:48 +0000 Subject: [PATCH 4/4] Fix: regenerate expo-video patch with correct format The patch file could not be parsed by patch-package 8.1.0-canary.1. Regenerated using git diff --no-index to produce valid hunk headers and correct blob hashes. Also fixed variable shadowing in the onplay handler's catch callback (e -> err) to avoid shadowing the event param. Co-authored-by: Abdelrahman Khattab --- .../expo-video+55.0.3+001+catch_play_abort_error.patch | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/patches/expo-video/expo-video+55.0.3+001+catch_play_abort_error.patch b/patches/expo-video/expo-video+55.0.3+001+catch_play_abort_error.patch index c4587df765ef..818a5a989f01 100644 --- a/patches/expo-video/expo-video+55.0.3+001+catch_play_abort_error.patch +++ b/patches/expo-video/expo-video+55.0.3+001+catch_play_abort_error.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/expo-video/build/VideoPlayer.web.js b/node_modules/expo-video/build/VideoPlayer.web.js -index 16df18b..a1b2c3d 100644 +index 10e49a3..52d0ab8 100644 --- a/node_modules/expo-video/build/VideoPlayer.web.js +++ b/node_modules/expo-video/build/VideoPlayer.web.js @@ -224,7 +224,7 @@ export default class VideoPlayerWeb extends globalThis.expo.SharedObject { @@ -38,11 +38,12 @@ index 16df18b..a1b2c3d 100644 } video.currentTime = firstVideo.currentTime; video.volume = firstVideo.volume; -@@ -305,6 +305,6 @@ export default class VideoPlayerWeb extends globalThis.expo.SharedObject { +@@ -305,7 +305,7 @@ export default class VideoPlayerWeb extends globalThis.expo.SharedObject { + this.playing = true; this._mountedVideos.forEach((mountedVideo) => { if (e.target !== mountedVideo) { - mountedVideo.play(); -+ mountedVideo.play()?.catch((e) => { if (e.name !== 'AbortError') throw e; }); ++ mountedVideo.play()?.catch((err) => { if (err.name !== 'AbortError') throw err; }); } }); };