diff --git a/ui/src/cast-viewer.tsx b/ui/src/cast-viewer.tsx index fa51b8a..46cf4f1 100644 --- a/ui/src/cast-viewer.tsx +++ b/ui/src/cast-viewer.tsx @@ -1,5 +1,5 @@ import React,{useEffect,useRef,useState} from 'react'; -import {IconArrowLeft,IconPlayerPause,IconPlayerPlay,IconVolume,IconVolumeOff,IconMaximize,IconMinimize,IconRefresh,IconAspectRatio} from '@tabler/icons-react'; +import {IconArrowLeft,IconPlayerPause,IconPlayerPlay,IconVolume,IconVolumeOff,IconMaximize,IconMinimize,IconRefresh} from '@tabler/icons-react'; import {CastRange} from './cast-range'; import {t} from './i18n'; @@ -37,7 +37,7 @@ export function CastVideo({active,onError,controls=true,aspectRatio,videoRef,onS } export function CastViewer(){ const video=useRef(null),root=useRef(null),timer=useRef|null>(null); - const [key,setKey]=useState(0),[error,setError]=useState(''),[state,setState]=useState<'connecting'|'playing'|'failed'>('connecting'),[visible,setVisible]=useState(true),[paused,setPaused]=useState(false),[muted,setMuted]=useState(true),[volume,setVolume]=useState(1),[fullscreen,setFullscreen]=useState(false),[fill,setFill]=useState(true); + const [key,setKey]=useState(0),[error,setError]=useState(''),[state,setState]=useState<'connecting'|'playing'|'failed'>('connecting'),[visible,setVisible]=useState(true),[paused,setPaused]=useState(false),[muted,setMuted]=useState(true),[volume,setVolume]=useState(1),[fullscreen,setFullscreen]=useState(false); function reveal(){setVisible(true);if(timer.current)clearTimeout(timer.current);if(state==='playing'&&!paused)timer.current=setTimeout(()=>{if(!root.current?.querySelector(':focus-visible'))setVisible(false);},3000);} useEffect(()=>{reveal();return()=>{if(timer.current)clearTimeout(timer.current);};},[state,paused,key]); useEffect(()=>{const changed=()=>setFullscreen(!!document.fullscreenElement);document.addEventListener('fullscreenchange',changed);return()=>document.removeEventListener('fullscreenchange',changed);},[]); @@ -46,11 +46,11 @@ export function CastViewer(){ function reconnect(){setError('');setPaused(false);setKey(v=>v+1);reveal();} function togglePlay(){const el=video.current;if(!el)return;if(el.paused)void el.play().catch(()=>setError(t('无法播放,请重新连接。')));else el.pause();reveal();} async function toggleFullscreen(){try{if(document.fullscreenElement)await document.exitFullscreen();else if(root.current?.requestFullscreen)await root.current.requestFullscreen();else (video.current as HTMLVideoElement&{webkitEnterFullscreen?:()=>void})?.webkitEnterFullscreen?.();}catch{setError(t('此浏览器暂不支持全屏。'));}reveal();} - return
{if(e.pointerType==='mouse')reveal();}} onClick={e=>{if(!(e.target as HTMLElement).closest('button,a,input')){reveal();}}} onFocusCapture={reveal} onBlurCapture={reveal} onKeyDown={e=>{if((e.target as HTMLElement).closest('button,a,input'))return;if(e.code==='Space'){e.preventDefault();togglePlay();}else if(e.key==='Escape')reveal();}}> + return
{if(e.pointerType==='mouse')reveal();}} onClick={e=>{if(!(e.target as HTMLElement).closest('button,a,input')){reveal();}}} onFocusCapture={reveal} onBlurCapture={reveal} onKeyDown={e=>{if((e.target as HTMLElement).closest('button,a,input'))return;if(e.code==='Space'){e.preventDefault();togglePlay();}else if(e.key==='Escape')reveal();}}> -
Framely{t('头显画面')}
{state==='playing'?t('实时画面'):state==='connecting'?t('正在连接'):t('连接已断开')}
+
{t('头显画面')}{state==='playing'?t('实时画面'):state==='connecting'?t('正在连接'):t('连接已断开')}
{state!=='playing'&&
{state==='connecting'?<>

{t('正在连接头显')}

{t('画面即将开始,请稍候。')}

:<>

{t('暂时无法观看')}

{error||t('确认头显已开始串流,然后重新连接。')}

}
} {error&&state==='playing'&&

{error}

} -
{setVolume(+e.target.value);setMuted(false);reveal();}}/>
{t('实时')}
+
; } diff --git a/ui/src/casting.css b/ui/src/casting.css index b72730b..72c5483 100644 --- a/ui/src/casting.css +++ b/ui/src/casting.css @@ -12,3 +12,5 @@ .cast-choice-group small{white-space:normal;line-height:1.45;max-width:100%;text-align:center}.cast-framing-section{scroll-margin-top:16px}.cast-framing-stage{max-width:min(640px,calc(100dvh - 330px))}@media(max-width:767px){.cast-choice-group button{min-width:0}.cast-framing-stage{max-width:100%}.cast-watch-icon{min-height:44px}} .cast-framing-stage{max-width:min(640px,calc(100dvh - 400px))}.cast-editor-actions{position:sticky;bottom:-24px;background:transparent;padding:14px 0;z-index:6}@media(max-width:767px){.cast-choice-group button>span{white-space:normal}.cast-framing-stage{max-width:100%}.cast-editor-actions{bottom:-16px}} .cast-protocol-heading{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:6px}.cast-protocol-heading h2{margin:0}.cast-protocol-heading .ui-switch{flex-shrink:0}.cast-range{--range-fill:0%;appearance:none;-webkit-appearance:none;display:block;min-height:32px!important;height:32px!important;padding:0!important;margin:0;background:transparent!important;cursor:pointer;border:0!important;outline-offset:3px;touch-action:pan-y}.cast-range::-webkit-slider-runnable-track{height:6px;border-radius:999px;background:linear-gradient(to right,var(--blue) var(--range-fill),#484848 var(--range-fill))}.cast-range::-webkit-slider-thumb{appearance:none;-webkit-appearance:none;width:28px;height:16px;margin-top:-5px;border:0;border-radius:999px;background:var(--blue);box-shadow:0 1px 3px #0005}.cast-range::-moz-range-track{height:6px;border-radius:999px;background:#484848}.cast-range::-moz-range-progress{height:6px;border-radius:999px;background:var(--blue)}.cast-range::-moz-range-thumb{width:28px;height:16px;border:0;border-radius:999px;background:var(--blue)}.cast-range:disabled{opacity:.4;cursor:default} +/* Viewer: one restrained floating toolbar; the video always remains complete. */ +.cast-watch video{object-fit:contain!important}.cast-watch-top{padding-bottom:40px;gap:12px;background:linear-gradient(rgb(0 0 0/.55),transparent)}.cast-watch-title{font-size:14px;font-weight:500;color:#e9e9e9}.cast-watch-connection{font-size:12px;color:#c6c6c6}.cast-watch-bottom{padding-top:32px;background:transparent;display:flex;justify-content:center}.cast-watch-controls{width:auto;max-width:100%;padding:6px 12px;gap:8px;background:rgb(18 18 18/.88);border:1px solid rgb(255 255 255/.12);border-radius:999px;box-shadow:0 8px 28px #0005;backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px)}.cast-watch .cast-watch-icon{width:40px;height:40px;min-height:40px;border:0;border-radius:50%;padding:8px;background:transparent;color:#ededed}.cast-watch .cast-watch-icon:hover{background:#ffffff12}.cast-watch-top .cast-watch-icon{background:transparent}.cast-watch-top .cast-watch-icon:hover{background:#ffffff12}.cast-watch-sound{gap:6px}.cast-watch-sound input{width:80px}.cast-watch-live{margin:0 12px 0 8px;font-size:11px;color:#bcbcbc;letter-spacing:.02em}.cast-watch-tools{gap:4px;padding-left:8px;border-left:1px solid #ffffff1f}.cast-watch .cast-range{--blue:#e7e7e7}.cast-watch .cast-range::-webkit-slider-thumb{width:20px;height:12px;margin-top:-3px}.cast-watch .cast-range::-moz-range-thumb{width:20px;height:12px}.cast-watch-message h1{font-size:22px;font-weight:500}.cast-watch-message p{font-size:13px}.cast-watch-message button{border-radius:999px;min-height:40px;font-size:13px;padding:10px 18px}@media(max-width:767px){.cast-watch-bottom{padding-bottom:calc(16px + env(safe-area-inset-bottom))}.cast-watch-controls{gap:4px;padding:5px 9px}.cast-watch-top{padding-top:calc(12px + env(safe-area-inset-top));padding-bottom:32px}.cast-watch-live{display:inline;margin:0 10px 0 4px}.cast-watch-title{font-size:13px}.cast-watch-tools{padding-left:6px;gap:2px}}@media(prefers-reduced-transparency:reduce){.cast-watch-controls{background:#181818;backdrop-filter:none;-webkit-backdrop-filter:none}}