Skip to content

Commit fb31cd7

Browse files
authored
(extension) make controls interactive in full screen video in main frame [DA-402] (#328)
1 parent 45500dc commit fb31cd7

File tree

9 files changed

+98
-13
lines changed

9 files changed

+98
-13
lines changed

packages/danmaku-anywhere/src/common/localization/locales/en/translation.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@
429429
}
430430
},
431431
"enableAnalytics": "Enable anonymous analytics",
432+
"enableFullscreenInteraction": "Enable fullscreen interaction",
432433
"help": {
433434
"feedback": "Provide feedback",
434435
"reportBug": "Report Bug"

packages/danmaku-anywhere/src/common/localization/locales/zh/translation.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,7 @@
416416
}
417417
},
418418
"enableAnalytics": "匿名数据收集",
419+
"enableFullscreenInteraction": "全屏时启用交互",
419420
"help": {
420421
"feedback": "提交意见或建议",
421422
"reportBug": "报告Bug"

packages/danmaku-anywhere/src/common/options/extensionOptions/constant.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export const defaultExtensionOptions: ExtensionOptions = {
3333
playerOptions: {
3434
showSkipButton: true,
3535
showDanmakuTimeline: true,
36+
enableFullscreenInteraction: true,
3637
},
3738
theme: {
3839
colorMode: ColorMode.System,

packages/danmaku-anywhere/src/common/options/extensionOptions/schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export const danmakuSourcesSchema = z.object({
5151
export const playerOptionsSchema = z.object({
5252
showSkipButton: z.boolean(),
5353
showDanmakuTimeline: z.boolean(),
54+
enableFullscreenInteraction: z.boolean(),
5455
})
5556

5657
export const retentionPolicySchema = z.object({

packages/danmaku-anywhere/src/common/options/extensionOptions/service.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ export class ExtensionOptionsService implements IStoreService {
165165
draft.playerOptions = {
166166
showSkipButton: true,
167167
showDanmakuTimeline: true,
168+
enableFullscreenInteraction: true,
168169
}
169170
}),
170171
})
@@ -248,6 +249,12 @@ export class ExtensionOptionsService implements IStoreService {
248249
})
249250
},
250251
})
252+
.version(23, {
253+
upgrade: (data) =>
254+
produce<ExtensionOptions>(data, (draft) => {
255+
draft.playerOptions.enableFullscreenInteraction = true
256+
}),
257+
})
251258
}
252259

253260
async get() {

packages/danmaku-anywhere/src/common/settings/settingConfigs.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,23 @@ export type ButtonSettingConfig = CommonSettingConfig & {
3434
export type SettingConfig<S> = ToggleSettingConfig<S>
3535

3636
const advancedSettings: SettingConfig<ExtensionOptions>[] = [
37+
{
38+
id: 'toggle.player.enableFullscreenInteraction',
39+
label: () =>
40+
i18n.t(
41+
'optionsPage.enableFullscreenInteraction',
42+
'Enable fullscreen interaction'
43+
),
44+
category: 'advanced',
45+
type: 'toggle',
46+
getValue: (options) => options.playerOptions.enableFullscreenInteraction,
47+
createUpdate: (options, newValue) => ({
48+
playerOptions: {
49+
...options.playerOptions,
50+
enableFullscreenInteraction: newValue,
51+
},
52+
}),
53+
},
3754
{
3855
id: 'toggle.analytics',
3956
label: () =>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// When video becomes full screen, the popover (content script) will be visible on top of the video,
2+
// but will become "inert" and non-interactive.
3+
// To maintain interactivity, we need to re-parent the popover into the fullscreen element.
4+
// https://github.com/whatwg/html/issues/10811
5+
export function reparentPopover(
6+
popover: HTMLDivElement,
7+
document: Document,
8+
target: Element | null
9+
) {
10+
if (!target) {
11+
// if no target, reparent to body and show the popover
12+
if (popover.parentElement !== document.body) {
13+
document.body.appendChild(popover)
14+
}
15+
popover.hidePopover()
16+
popover.showPopover()
17+
return
18+
}
19+
20+
if (
21+
target instanceof HTMLVideoElement ||
22+
target instanceof HTMLIFrameElement
23+
) {
24+
// Cannot reparent to these elements, hide and show so the popover will be visible on top of the video
25+
popover.hidePopover()
26+
popover.showPopover()
27+
} else {
28+
// reparent to target and show the popover
29+
if (popover.parentElement !== target) {
30+
target.appendChild(popover)
31+
}
32+
popover.hidePopover()
33+
popover.showPopover()
34+
}
35+
}

packages/danmaku-anywhere/src/content/controller/danmaku/frame/FrameManager.tsx

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import { useEventCallback } from '@mui/material'
2-
import { useEffect } from 'react'
2+
import { useEffect, useRef } from 'react'
33
import { useTranslation } from 'react-i18next'
44
import { useToast } from '@/common/components/Toast/toastStore'
55
import { IS_STANDALONE_RUNTIME } from '@/common/environment/isStandalone'
66
import { uiContainer } from '@/common/ioc/uiIoc'
77
import { Logger } from '@/common/Logger'
8+
import { useExtensionOptions } from '@/common/options/extensionOptions/useExtensionOptions'
89
import { createRpcServer } from '@/common/rpc/server'
910
import { playerRpcClient } from '@/common/rpcClient/background/client'
1011
import type { PlayerRelayEvents } from '@/common/rpcClient/background/types'
12+
import { reparentPopover } from '@/content/common/reparentPopover'
1113
import { CONTROLLER_ROOT_ID } from '@/content/controller/common/constants/rootId'
1214
import { useActiveConfig } from '@/content/controller/common/context/useActiveConfig'
1315
import { useUnmountDanmaku } from '@/content/controller/common/hooks/useUnmountDanmaku'
@@ -25,6 +27,14 @@ export const FrameManager = () => {
2527
const { toast } = useToast()
2628

2729
const config = useActiveConfig()
30+
const { data: extensionOptions } = useExtensionOptions()
31+
32+
const enableFullscreenInteraction =
33+
extensionOptions.playerOptions.enableFullscreenInteraction
34+
35+
const enableFullscreenInteractionRef = useRef(enableFullscreenInteraction)
36+
37+
enableFullscreenInteractionRef.current = enableFullscreenInteraction
2838

2939
const setVideoId = useStore.use.setVideoId()
3040
const { activeFrame, setActiveFrame, updateFrame } = useStore.use.frame()
@@ -106,9 +116,15 @@ export const FrameManager = () => {
106116
const root: HTMLDivElement | null = document.querySelector(
107117
`#${CONTROLLER_ROOT_ID}`
108118
)
109-
if (root) {
110-
root.hidePopover()
111-
root.showPopover()
119+
if (!root) {
120+
return
121+
}
122+
123+
if (enableFullscreenInteractionRef.current) {
124+
const fullscreenElement = document.fullscreenElement
125+
reparentPopover(root, document, fullscreenElement)
126+
} else {
127+
reparentPopover(root, document, null)
112128
}
113129
},
114130
},

packages/danmaku-anywhere/src/content/player/index.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { createPipWindow, moveElement } from '@/content/player/pipUtils'
2121
import { VideoEventService } from '@/content/player/videoEvent/VideoEvent.service'
2222
import { VideoNodeObserverService } from '@/content/player/videoObserver/VideoNodeObserver.service'
2323
import { VideoSkipService } from '@/content/player/videoSkip/VideoSkip.service'
24+
import { reparentPopover } from '../common/reparentPopover'
2425

2526
const { data: frameId } = await chromeRpcClient.getFrameId()
2627

@@ -155,6 +156,8 @@ danmakuOptionsService.get().then((options) => {
155156

156157
const extensionOptionsService = uiContainer.get(ExtensionOptionsService)
157158

159+
let enableFullscreenInteraction = true
160+
158161
extensionOptionsService.get().then((options) => {
159162
if (options.playerOptions.showSkipButton) {
160163
videoSkipService.enable()
@@ -166,6 +169,8 @@ extensionOptionsService.get().then((options) => {
166169
} else {
167170
danmakuDensityService.disable()
168171
}
172+
enableFullscreenInteraction =
173+
options.playerOptions.enableFullscreenInteraction
169174
})
170175

171176
extensionOptionsService.onChange((options) => {
@@ -179,21 +184,22 @@ extensionOptionsService.onChange((options) => {
179184
} else {
180185
danmakuDensityService.disable()
181186
}
187+
enableFullscreenInteraction =
188+
options.playerOptions.enableFullscreenInteraction
182189
})
183190

184191
/**
185192
* Window events
186193
*/
187194
document.addEventListener('fullscreenchange', () => {
188-
/**
189-
* The last element in the top layer is shown on top.
190-
* Hiding then showing the popover will make it the last element in the top layer.
191-
*
192-
* Do this every time something goes fullscreen, to ensure the popover is always on top.
193-
*/
194-
root.hidePopover()
195-
root.showPopover()
196-
// Then notify the controller so that the controller can also toggle popover to stay on top
195+
if (enableFullscreenInteraction) {
196+
const fullscreenElement = document.fullscreenElement
197+
reparentPopover(root, document, fullscreenElement)
198+
} else {
199+
reparentPopover(root, document, null)
200+
}
201+
202+
// Notify the controller so it can also handle its popover
197203
void playerRpcClient.controller['relay:event:showPopover']({ frameId })
198204
})
199205

0 commit comments

Comments
 (0)