diff --git a/packages/artplayer/src/control/playAndPause.js b/packages/artplayer/src/control/playAndPause.js index a23f1b019..957e75e91 100644 --- a/packages/artplayer/src/control/playAndPause.js +++ b/packages/artplayer/src/control/playAndPause.js @@ -1,4 +1,4 @@ -import { append, setStyle, tooltip } from '../utils' +import { append, setStyle, silencePromise, tooltip } from '../utils' export default function playAndPause(option) { return art => ({ @@ -12,7 +12,7 @@ export default function playAndPause(option) { tooltip($pause, i18n.get('Pause')) proxy($play, 'click', () => { - art.play() + silencePromise(art.play()) }) proxy($pause, 'click', () => { diff --git a/packages/artplayer/src/events/clickInit.js b/packages/artplayer/src/events/clickInit.js index ec5618bb0..3e86a09c4 100644 --- a/packages/artplayer/src/events/clickInit.js +++ b/packages/artplayer/src/events/clickInit.js @@ -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 { diff --git a/packages/artplayer/src/hotkey.js b/packages/artplayer/src/hotkey.js index 28d922036..2a5cd6367 100644 --- a/packages/artplayer/src/hotkey.js +++ b/packages/artplayer/src/hotkey.js @@ -1,4 +1,4 @@ -import { isMobile } from './utils' +import { isMobile, silencePromise } from './utils' export default class Hotkey { constructor(art) { @@ -21,7 +21,7 @@ export default class Hotkey { }) this.add('Space', () => { - this.art.toggle() + silencePromise(this.art.toggle()) }) this.add('ArrowLeft', () => { diff --git a/packages/artplayer/src/mask.js b/packages/artplayer/src/mask.js index c92875111..7449454ad 100644 --- a/packages/artplayer/src/mask.js +++ b/packages/artplayer/src/mask.js @@ -1,4 +1,4 @@ -import { append, setStyle } from './utils' +import { append, setStyle, silencePromise } from './utils' import Component from './utils/component' export default class Mask extends Component { @@ -18,6 +18,6 @@ export default class Mask extends Component { setStyle($error, 'display', null) }) - events.proxy(template.$state, 'click', () => art.play()) + events.proxy(template.$state, 'click', () => silencePromise(art.play())) } } diff --git a/packages/artplayer/src/player/eventInit.js b/packages/artplayer/src/player/eventInit.js index c5effc436..f265c7635 100644 --- a/packages/artplayer/src/player/eventInit.js +++ b/packages/artplayer/src/player/eventInit.js @@ -1,5 +1,5 @@ import config from '../config' -import { addClass, isMobile, setStyle, sleep } from '../utils' +import { addClass, isMobile, setStyle, silencePromise, sleep } from '../utils' export default function eventInit(art) { const { @@ -51,7 +51,7 @@ export default function eventInit(art) { art.on('video:ended', () => { if (option.loop) { art.seek = 0 - art.play() + silencePromise(art.play()) art.controls.show = false art.mask.show = false } diff --git a/packages/artplayer/src/player/miniMix.js b/packages/artplayer/src/player/miniMix.js index f54c37196..158531bc0 100644 --- a/packages/artplayer/src/player/miniMix.js +++ b/packages/artplayer/src/player/miniMix.js @@ -1,4 +1,4 @@ -import { addClass, append, createElement, def, getRect, hasClass, isInViewport, removeClass, setStyle } from '../utils' +import { addClass, append, createElement, def, getRect, hasClass, isInViewport, removeClass, setStyle, silencePromise } from '../utils' export default function miniMix(art) { const { @@ -53,7 +53,7 @@ export default function miniMix(art) { const $state = append($mini, `
`) const $play = append($state, icons.play) const $pause = append($state, icons.pause) - proxy($play, 'click', () => art.play()) + proxy($play, 'click', () => silencePromise(art.play())) proxy($pause, 'click', () => art.pause()) initState($play, $pause) art.on('video:playing', () => initState($play, $pause)) diff --git a/packages/artplayer/src/player/switchMix.js b/packages/artplayer/src/player/switchMix.js index 186005e7b..f8913f981 100644 --- a/packages/artplayer/src/player/switchMix.js +++ b/packages/artplayer/src/player/switchMix.js @@ -1,4 +1,4 @@ -import { def } from '../utils' +import { def, silencePromise } from '../utils' export default function switchMix(art) { function switchUrl(url, currentTime) { @@ -31,7 +31,7 @@ export default function switchMix(art) { art.aspectRatio = aspectRatio if (playing) { - await art.play() + await silencePromise(art.play()) } art.notice.show = '' diff --git a/packages/artplayer/src/plugins/autoPlayback.js b/packages/artplayer/src/plugins/autoPlayback.js index 9f065249c..e36aaf4f0 100644 --- a/packages/artplayer/src/plugins/autoPlayback.js +++ b/packages/artplayer/src/plugins/autoPlayback.js @@ -1,4 +1,4 @@ -import { append, query, secondToTime, setStyle } from '../utils' +import { append, query, secondToTime, setStyle, silencePromise } from '../utils' export default function autoPlayback(art) { const { @@ -57,7 +57,7 @@ export default function autoPlayback(art) { proxy($jump, 'click', () => { art.seek = currentTime - art.play() + silencePromise(art.play()) setStyle($poster, 'display', 'none') setStyle($autoPlayback, 'display', 'none') }) diff --git a/packages/artplayer/src/utils/error.js b/packages/artplayer/src/utils/error.js index 5b0e87495..f0ebe46da 100644 --- a/packages/artplayer/src/utils/error.js +++ b/packages/artplayer/src/utils/error.js @@ -14,3 +14,28 @@ export function errorHandle(condition, msg) { } return condition } + +/** + * Attach a no-op catch to a promise that is intentionally not awaited. + * + * `art.play()` resolves the native `video.play()` promise, which rejects when the + * request is interrupted (a `pause()`, a `load()`, a new `src`, or the element + * being removed while the request is still pending) or when autoplay is blocked. + * Internal call sites fire it from event handlers and cannot await it, so the + * rejection escapes as a global `unhandledrejection` and error trackers report it + * as a crash even though the player recovers on its own. + * + * Nothing is hidden from users of the public API: `art.play()` and `art.toggle()` + * still return the original promise, so an application that calls them keeps its + * own rejection to handle. Only the internal, non-awaited copy is silenced. + * + * Returns a promise that always fulfills, so an internal caller may await it + * without inheriting the rejection. A non-thenable value is returned untouched + * (`art.toggle()` yields `art.pause()`, which is synchronous). + */ +export function silencePromise(value) { + if (value && typeof value.catch === 'function') { + return value.catch(() => {}) + } + return value +} diff --git a/packages/artplayer/types/utils.d.ts b/packages/artplayer/types/utils.d.ts index 2f32b794b..af4f345de 100644 --- a/packages/artplayer/types/utils.d.ts +++ b/packages/artplayer/types/utils.d.ts @@ -43,6 +43,7 @@ export interface Utils { loadImg: (url: string, scale?: number) => Promise