diff --git a/special-pages/pages/duckplayer/app/components/InfoBar.jsx b/special-pages/pages/duckplayer/app/components/InfoBar.jsx index 0c5f6297d1..630fd46148 100644 --- a/special-pages/pages/duckplayer/app/components/InfoBar.jsx +++ b/special-pages/pages/duckplayer/app/components/InfoBar.jsx @@ -11,6 +11,7 @@ import { SwitchContext, SwitchProvider } from '../providers/SwitchProvider.jsx'; import { Tooltip } from './Tooltip.jsx'; import { useSetFocusMode } from './FocusMode.jsx'; import { useTypedTranslation } from '../types.js'; +import { usePlaybackControl } from './PlaybackControl'; /** * @param {object} props @@ -99,6 +100,7 @@ export function InfoIcon({ debugStyles = false }) { */ function ControlBarDesktop({ embed }) { const settingsUrl = useSettingsUrl(); + const { pauseVideo } = usePlaybackControl(); const openOnYoutube = useOpenOnYoutubeHandler(); const { t } = useTypedTranslation(); const { state } = useContext(SwitchContext); @@ -120,7 +122,10 @@ function ControlBarDesktop({ embed }) { formfactor={'desktop'} buttonProps={{ onClick: () => { - if (embed) openOnYoutube(embed); + if (embed) { + pauseVideo(); + openOnYoutube(embed); + } }, }} > diff --git a/special-pages/pages/duckplayer/app/components/PlaybackControl.jsx b/special-pages/pages/duckplayer/app/components/PlaybackControl.jsx new file mode 100644 index 0000000000..c088bf760c --- /dev/null +++ b/special-pages/pages/duckplayer/app/components/PlaybackControl.jsx @@ -0,0 +1,15 @@ +export const EVENT_PLAY = 'ddg-duckplayer-play'; +export const EVENT_PAUSE = 'ddg-duckplayer-pause'; + +export function usePlaybackControl() { + return { + playVideo: () => { + window.dispatchEvent(new Event(EVENT_PLAY)); + console.log(EVENT_PLAY); + }, + pauseVideo: () => { + window.dispatchEvent(new Event(EVENT_PAUSE)); + console.log(EVENT_PAUSE); + }, + }; +} diff --git a/special-pages/pages/duckplayer/app/components/Player.jsx b/special-pages/pages/duckplayer/app/components/Player.jsx index 1420dc43da..89811ce278 100644 --- a/special-pages/pages/duckplayer/app/components/Player.jsx +++ b/special-pages/pages/duckplayer/app/components/Player.jsx @@ -102,6 +102,7 @@ function useIframeEffects(src) { features.clickCapture(), features.titleCapture(), features.mouseCapture(), + features.playbackEvents(), ]; /** diff --git a/special-pages/pages/duckplayer/app/features/iframe.js b/special-pages/pages/duckplayer/app/features/iframe.js index b252724b2a..3c9515d84b 100644 --- a/special-pages/pages/duckplayer/app/features/iframe.js +++ b/special-pages/pages/duckplayer/app/features/iframe.js @@ -3,6 +3,7 @@ import { AutoFocus } from './autofocus.js'; import { ClickCapture } from './click-capture.js'; import { TitleCapture } from './title-capture.js'; import { MouseCapture } from './mouse-capture.js'; +import { PlaybackEvents } from './playback-events.js'; /** * Represents an individual piece of functionality in the iframe. @@ -74,5 +75,14 @@ export function createIframeFeatures(settings) { mouseCapture: () => { return new MouseCapture(); }, + /** + * @return {IframeFeature} + */ + playbackEvents: () => { + if (settings.playbackEvents) { + return new PlaybackEvents(); + } + return IframeFeature.noop(); + }, }; } diff --git a/special-pages/pages/duckplayer/app/features/playback-events.js b/special-pages/pages/duckplayer/app/features/playback-events.js new file mode 100644 index 0000000000..6dd953420e --- /dev/null +++ b/special-pages/pages/duckplayer/app/features/playback-events.js @@ -0,0 +1,30 @@ +import { EVENT_PLAY, EVENT_PAUSE } from '../components/PlaybackControl.jsx'; + +/** + * @typedef {import("./iframe").IframeFeature} IframeFeature + */ + +/** + * Exposes video element in iframe. + * + * @implements IframeFeature + */ +export class PlaybackEvents { + /** + * @param {HTMLIFrameElement} iframe + */ + iframeDidLoad(iframe) { + const document = iframe.contentWindow?.document; + + const playHandler = () => document?.querySelector('video')?.play(); + window.addEventListener(EVENT_PLAY, playHandler); + + const pauseHandler = () => document?.querySelector('video')?.pause(); + window.addEventListener(EVENT_PAUSE, pauseHandler); + + return () => { + window.removeEventListener(EVENT_PLAY, playHandler); + window.removeEventListener(EVENT_PAUSE, pauseHandler); + }; + } +} diff --git a/special-pages/pages/duckplayer/app/settings.js b/special-pages/pages/duckplayer/app/settings.js index f0f52adc70..5f398bf479 100644 --- a/special-pages/pages/duckplayer/app/settings.js +++ b/special-pages/pages/duckplayer/app/settings.js @@ -103,4 +103,13 @@ export class Settings { return 'desktop'; } } + + /** + * Enables sending events to video embed + * + * @returns {boolean} + */ + get playbackEvents() { + return this.layout === 'desktop'; + } } diff --git a/special-pages/pages/duckplayer/integration-tests/duck-player.js b/special-pages/pages/duckplayer/integration-tests/duck-player.js index 4ea48c3ee6..aa9a00c511 100644 --- a/special-pages/pages/duckplayer/integration-tests/duck-player.js +++ b/special-pages/pages/duckplayer/integration-tests/duck-player.js @@ -396,6 +396,24 @@ export class DuckPlayerPage { }); } + async firesPauseEventWhenOpeningInYoutube() { + const expectedEvent = 'ddg-duckplayer-pause'; + /** + * @type {Promise} + */ + const evaluatePromise = this.page.evaluate((event) => { + return new Promise((resolve) => { + window.addEventListener(event, () => { + resolve(true); + }); + }); + }, expectedEvent); + + await this.page.getByRole('button', { name: 'Watch on YouTube' }).click(); + + await evaluatePromise; + } + /** * @return {Promise} */ diff --git a/special-pages/pages/duckplayer/integration-tests/duckplayer.spec.js b/special-pages/pages/duckplayer/integration-tests/duckplayer.spec.js index d2aa8456dc..438973786b 100644 --- a/special-pages/pages/duckplayer/integration-tests/duckplayer.spec.js +++ b/special-pages/pages/duckplayer/integration-tests/duckplayer.spec.js @@ -94,6 +94,14 @@ test.describe('duckplayer iframe', () => { await duckplayer.hasLoadedIframe(); await duckplayer.focusModeIsAbsent(); }); + test('fires pause when clicking on Watch on YouTube', async ({ page }, workerInfo) => { + test.skip(isMobile(workerInfo)); + const duckplayer = DuckPlayerPage.create(page, workerInfo); + // load as normal + await duckplayer.openWithVideoID(); + await duckplayer.hasLoadedIframe(); + await duckplayer.firesPauseEventWhenOpeningInYoutube(); + }); }); test.describe('duckplayer toolbar', () => {