Skip to content

Commit

Permalink
add initial data to duckplayer page (#971)
Browse files Browse the repository at this point in the history
* add initial data to duckplayer page

* opt-out of the extract retry loop

* Enable PiP button in DuckPlayer (#973)

* Enable pip button in duckplayer

* fix an exception in the DuckPlayer integration test

* Remove redundant console logs

* Lint fix

* Fix integration tests for duckplayer

* rename to 'state'

---------

Co-authored-by: Shane Osbourne <sosbourne@duckduckgo.com>

---------

Co-authored-by: Shane Osbourne <sosbourne@duckduckgo.com>
Co-authored-by: Maxim Tsoy <mtsoy@duckduckgo.com>
  • Loading branch information
3 people authored Jun 14, 2024
1 parent a327af5 commit d91c50c
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 9 deletions.
44 changes: 36 additions & 8 deletions packages/special-pages/pages/duckplayer/src/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,16 @@ const VideoPlayer = {
* @param {object} opts
* @param {string} opts.base
* @param {ImportMeta['env']} opts.env
* @param {import('./messages').DuckPlayerPageSettings} opts.settings
*/
init: (opts) => {
VideoPlayer.loadVideoById()
VideoPlayer.autoFocusVideo(opts.env)
VideoPlayer.setTabTitle()
VideoPlayer.setClickListener(opts.base)
if (opts.settings.pip.state === 'enabled') {
VideoPlayer.enablePiP()
}
},

/**
Expand Down Expand Up @@ -232,6 +236,26 @@ const VideoPlayer = {
})
},

enablePiP: () => {
VideoPlayer.onIframeLoaded(() => {
try {
const iframe = VideoPlayer.iframe()
const iframeDocument = iframe?.contentDocument
const iframeWindow = iframe?.contentWindow
if (iframeDocument && iframeWindow) {
// @ts-expect-error - typescript doesn't know about CSSStyleSheet here for some reason
const styleSheet = new iframeWindow.CSSStyleSheet()
styleSheet.replaceSync('button.ytp-pip-button { display: inline-block !important; }')
// See https://developer.mozilla.org/en-US/docs/Web/API/Document/adoptedStyleSheets#append_a_new_stylesheet
iframeDocument.adoptedStyleSheets = [...iframeDocument.adoptedStyleSheets, styleSheet]
}
} catch (e) {
// ignore errors
console.warn(e)
}
})
},

/**
* Wait for the video to load and then focus it
* @param {ImportMeta['env']} env
Expand Down Expand Up @@ -412,16 +436,18 @@ const Comms = {
* `window.postMessage({ alwaysOpenSetting: false })`
*
* @param {DuckPlayerPageMessages} messaging
* @return {Promise<{value: import('./messages').InitialSetup} | { error: string}>}
*/
init: async (messaging) => {
// try to make communication with the native side.
const result = await callWithRetry(() => {
return messaging.getUserValues()
return messaging.initialSetup()
})
// if we received a connection, use the initial values
if ('value' in result) {
Comms.messaging = messaging
if ('enabled' in result.value.privatePlayerMode) {
const { userValues } = result.value
if ('enabled' in userValues.privatePlayerMode) {
Setting.setState(true)
} else {
Setting.setState(false)
Expand All @@ -434,9 +460,8 @@ const Comms = {
Setting.setState(false)
}
})
} else {
console.error(result.error)
}
return result
},
/**
* From the player page, all we can do is 'setUserValues' to {enabled: {}}
Expand Down Expand Up @@ -808,15 +833,18 @@ document.addEventListener('DOMContentLoaded', async () => {

const page = new DuckPlayerPageMessages(messaging, import.meta.injectName)

await Comms.init(page)
const result = await Comms.init(page)

if (!Comms.messaging) {
console.warn('cannot continue as messaging was not resolved')
if (!('value' in result)) {
console.warn('cannot continue as the initialSetup call didnt complete')
console.error(result.error)
return
}

VideoPlayer.init({
base: baseUrl(import.meta.injectName),
env: import.meta.env
env: import.meta.env,
settings: result.value.settings
})
Tooltip.init()
PlayOnYouTube.init({
Expand Down
49 changes: 49 additions & 0 deletions packages/special-pages/pages/duckplayer/src/js/messages.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
/**
* @typedef {object} InitialSetup - The initial payload used to communicate render-blocking information
* @property {UserValues} userValues - The state of the user values
* @property {DuckPlayerPageSettings} settings - Additional settings
*/

/**
* Notifications or requests that the Duck Player Page will
* send to the native side
Expand All @@ -16,6 +22,28 @@ export class DuckPlayerPageMessages {
this.injectName = injectName
}

/**
* This is sent when the user wants to set Duck Player as the default.
*
* @returns {Promise<InitialSetup>} params
*/
initialSetup () {
if (this.injectName === 'integration') {
return Promise.resolve({
settings: {
pip: {
state: 'enabled'
}
},
userValues: new UserValues({
overlayInteracted: false,
privatePlayerMode: { alwaysAsk: {} }
})
})
}
return this.messaging.request('initialSetup')
}

/**
* This is sent when the user wants to set Duck Player as the default.
*
Expand Down Expand Up @@ -100,6 +128,27 @@ export class UserValues {
}
}

/**
* Sent in the initial page load request. Used to provide features toggles
* and other none-user-specific settings.
*
* Note: This will be improved soon with better remote config integration.
*/
export class DuckPlayerPageSettings {
/**
* @param {object} params
* @param {object} params.pip
* @param {"enabled" | "disabled"} params.pip.state
*/
constructor (params) {
/**
* 'enabled' means that the FE should show the PIP button
* 'disabled' means that the FE should never show it
*/
this.pip = params.pip
}
}

/**
* This will return either { value: awaited value },
* { error: error message }
Expand Down
15 changes: 14 additions & 1 deletion packages/special-pages/tests/page-objects/duck-player.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,19 @@ export class DuckPlayerPage {
})
// default mocks - just enough to render the first page without error
this.mocks.defaultResponses({
/** @type {Awaited<ReturnType<import("../../pages/duckplayer/src/js/index.js").DuckPlayerPageMessages['initialSetup']>>} */
initialSetup: {
settings: {
pip: {
state: 'disabled'
}
},
userValues: {
privatePlayerMode: { alwaysAsk: {} },
overlayInteracted: false
}

},
/** @type {import("../../pages/duckplayer/src/js/index.js").UserValues} */
getUserValues: {
privatePlayerMode: { alwaysAsk: {} },
Expand Down Expand Up @@ -299,7 +312,7 @@ export class DuckPlayerPage {
* @return {Promise<void>}
*/
async didReceiveFirstSettingsUpdate () {
await this.mocks.waitForCallCount({ count: 1, method: 'getUserValues' })
await this.mocks.waitForCallCount({ count: 1, method: 'initialSetup' })
}

async toggleAlwaysOpenSetting () {
Expand Down

0 comments on commit d91c50c

Please sign in to comment.