fix: silence unhandled play() rejections from internal callers

`art.play()` is an async wrapper around the native `video.play()` promise, which
rejects when a pending play request is interrupted (`pause()`, `load()`, a new
`src`, or the element being removed) and when autoplay is blocked.

Every internal caller fires it from an event handler and cannot await it, so the
rejection escapes as a global `unhandledrejection`. Error trackers report it as
an uncaught crash even though the player recovers on its own — for example
destroying the player right after mount, which is routine when the video sits in
a modal the user can close quickly.

Add a `silencePromise` util (the same approach video.js uses) and apply it to the
non-awaited internal call sites: mask, play control, mini player, loop restart,
autoPlayback jump, hotkey and click toggles.

`switchMix` is fixed too, for a second symptom: `await art.play()` inside the
async `video:canplay` handler both leaked the rejection and left the `switchUrl`
promise pending forever, since the throw never reached the enclosing executor's
`reject`. It now settles.

The public API is unchanged: `art.play()` and `art.toggle()` still return the
original promise, so applications that call them keep their own rejection to
handle. Only the internal, non-awaited copy is silenced.
This commit is contained in:
Anıl Çalışkol committed 2026-08-30 16:31:40 +03:00
1 parent 0b9c31a8cf
commit 0f47e11d63
10 files changed
+44 -18

No files matched your search

+4 -4
View File
@@ -1,4 +1,4 @@
import { includeFromEvent, isMobile } from '../utils'
import { includeFromEvent, isMobile, silencePromise } from '../utils'
export default function clickInit(art, events) {
const {
@@ -35,11 +35,11 @@ export default function clickInit(art, events) {
if (isMobile) {
if (!art.isLock && MOBILE_CLICK_PLAY) {
art.toggle()
silencePromise(art.toggle())
}
}
else {
art.toggle()
silencePromise(art.toggle())
}
clickTimes = clicks
break
@@ -48,7 +48,7 @@ export default function clickInit(art, events) {
if (isMobile) {
if (!art.isLock && MOBILE_DBCLICK_PLAY) {
art.toggle()
silencePromise(art.toggle())
}
}
else {