diff --git a/src/components/MediaHeaderControls.jsx b/src/components/MediaHeaderControls.jsx
new file mode 100644
index 0000000..f179e19
--- /dev/null
+++ b/src/components/MediaHeaderControls.jsx
@@ -0,0 +1,78 @@
+import { useState, useEffect } from 'react';
+import Icon from './Icons';
+import { onMediaUiState, mediaCommand } from '../utils/mediaControl';
+
+/* =========================================================
+ 页眉媒体控制区 —— 仅媒体轮播页显示
+ 订阅媒体页播放状态(paused/index/total/name/soundBlocked),
+ 点击通过 mediaCommand() 下发指令,由 MediaScreen 执行
+ ========================================================= */
+
+/** 展示名清洗:URL 只取文件名,不显示完整播放地址 */
+function cleanName(name) {
+ if (!name) return '';
+ const text = String(name);
+ try {
+ const u = new URL(text);
+ const last = decodeURIComponent(u.pathname.split('/').filter(Boolean).pop() || '');
+ return last ? `${last}${u.search ? ' …' : ''}` : text;
+ } catch {
+ return text;
+ }
+}
+
+export default function MediaHeaderControls() {
+ const [st, setSt] = useState(null);
+
+ useEffect(() => onMediaUiState(setSt), []);
+
+ if (!st || !st.loaded) {
+ return (
+
+ {/* 媒体轮播页:页眉内嵌播放控制 */}
+ {location.pathname === '/screen' &&
}
{left && (
)}
- {/* 右下角轻量播放控制(弱化) */}
-
- {/* 播放/暂停 */}
-
- {/* 上一段 / 下一段 */}
-
-
-
- {currentItem ? `${index + 1}` : '0'}
- / {list.length}
-
-
-
- {/* 当前播放(仅文件名,不显示完整地址;点击进入后台) */}
-
-
- {currentItem ? cleanName(currentItem.name) : '等待播放…'}
-
- {/* 声音 */}
-
-
- 点击开启声音
-
-
+ {/* 播放控制已上移页眉(PageHeader 内渲染,媒体区保持纯净全屏) */}
{/* 页脚:园区概览(与数据大屏一致) */}
diff --git a/src/styles/datascreen.css b/src/styles/datascreen.css
index 14ed484..c66ec80 100644
--- a/src/styles/datascreen.css
+++ b/src/styles/datascreen.css
@@ -2461,3 +2461,96 @@
.bd-cell-jobs { grid-column: 5; }
/* 底部园区概览(见 .bd-strip 区块) */
+
+/* ============ 页眉媒体播放控制(仅媒体轮播页) ============ */
+.bd-hd-media {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ padding: 3px 8px;
+ background: rgba(255, 255, 255, 0.7);
+ border: 1px solid rgba(47, 107, 255, 0.14);
+ border-radius: 9px;
+ flex-shrink: 0;
+ backdrop-filter: blur(8px);
+ -webkit-backdrop-filter: blur(8px);
+}
+.bd-hd-media-btn {
+ width: 24px;
+ height: 24px;
+ border: none;
+ border-radius: 6px;
+ background: transparent;
+ color: var(--bd-blue);
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+.bd-hd-media-btn:hover {
+ background: rgba(47, 107, 255, 0.12);
+}
+.bd-hd-media-btn.primary {
+ color: #fff;
+ background: linear-gradient(140deg, var(--bd-blue), var(--bd-cyan));
+ box-shadow: 0 3px 8px rgba(47, 107, 255, 0.3);
+}
+.bd-hd-media-btn.warn {
+ color: var(--bd-orange);
+}
+.bd-hd-media-num {
+ font-family: "JetBrains Mono", "Oswald", monospace;
+ font-size: 11px;
+ color: var(--bd-sub);
+ display: inline-flex;
+ align-items: baseline;
+ gap: 2px;
+ padding: 0 2px;
+}
+.bd-hd-media-num b {
+ font-size: 12px;
+ color: var(--bd-ink);
+}
+.bd-hd-media-num em {
+ font-style: normal;
+ font-size: 10px;
+}
+.bd-hd-media-name {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+ max-width: 160px;
+ padding: 3px 8px;
+ border: none;
+ border-radius: 6px;
+ background: transparent;
+ font-size: 11px;
+ font-weight: 600;
+ font-family: inherit;
+ color: var(--bd-sub);
+ cursor: pointer;
+ overflow: hidden;
+}
+.bd-hd-media-name > span:last-child {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.bd-hd-media-name:hover {
+ background: rgba(47, 107, 255, 0.08);
+ color: var(--bd-ink);
+}
+.bd-hd-media-dot {
+ width: 6px;
+ height: 6px;
+ flex-shrink: 0;
+ border-radius: 50%;
+ background: var(--bd-green);
+ box-shadow: 0 0 6px rgba(23, 178, 106, 0.8);
+ animation: bdPulse 1.6s ease-in-out infinite;
+}
+/* 窄屏:页眉控制区收起名称,保留核心按钮 */
+@media (max-width: 1280px) {
+ .bd-hd-media-name { display: none; }
+}
diff --git a/src/styles/mediascreen.css b/src/styles/mediascreen.css
index 6758dc3..fd61b71 100644
--- a/src/styles/mediascreen.css
+++ b/src/styles/mediascreen.css
@@ -67,13 +67,14 @@
-webkit-backdrop-filter: blur(14px);
}
-/* 媒体播放器(铺满媒体区,等比适配) */
+/* 媒体播放器(填满整个媒体区,等比裁切铺满) */
.ms-media {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
- object-fit: contain;
+ object-fit: cover;
+ object-position: center;
background: transparent;
}
@@ -109,127 +110,7 @@
border-bottom: 1px solid rgba(47, 107, 255, 0.3);
}
-/* 右下角轻量播放控制(弱化悬浮) */
-.ms-controls {
- position: absolute;
- right: 16px;
- bottom: 14px;
- left: auto;
- transform: none;
- z-index: 10;
- display: flex;
- align-items: center;
- gap: 8px;
- padding: 6px 12px;
- background: rgba(255, 255, 255, 0.55);
- border: 1px solid rgba(120, 160, 220, 0.16);
- border-radius: 30px;
- backdrop-filter: blur(12px);
- -webkit-backdrop-filter: blur(12px);
- box-shadow: 0 6px 22px rgba(56, 100, 170, 0.12);
- opacity: 0;
- transition: opacity 0.5s ease;
-}
-.ms-controls.visible { opacity: 1; }
-
-.ms-play {
- width: 30px;
- height: 30px;
- border-radius: 50%;
- border: none;
- display: flex;
- align-items: center;
- justify-content: center;
- color: #fff;
- background: linear-gradient(140deg, var(--bd-blue, #2f6bff), var(--bd-cyan, #4cc2ff));
- cursor: pointer;
- box-shadow: 0 4px 12px rgba(47, 107, 255, 0.3);
- transition: transform 0.2s;
-}
-.ms-play:hover { transform: scale(1.08); }
-
-.ms-nav {
- display: flex;
- align-items: center;
- gap: 6px;
-}
-.ms-nav button {
- width: 26px;
- height: 26px;
- border-radius: 50%;
- border: 1px solid rgba(47, 107, 255, 0.18);
- background: rgba(255, 255, 255, 0.6);
- color: var(--bd-blue, #2f6bff);
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- transition: all 0.2s;
-}
-.ms-nav button:hover {
- background: rgba(47, 107, 255, 0.12);
- border-color: rgba(47, 107, 255, 0.5);
-}
-.ms-nav-num {
- display: inline-flex;
- align-items: baseline;
- gap: 3px;
- font-family: "JetBrains Mono", "Oswald", monospace;
-}
-.ms-nav-num b {
- font-size: 13px;
- color: var(--bd-ink, #1d2c44);
-}
-.ms-nav-num em {
- font-style: normal;
- font-size: 10.5px;
- color: var(--bd-sub, #7d8fa9);
-}
-
-.ms-now {
- display: flex;
- align-items: center;
- gap: 6px;
- padding-left: 10px;
- border-left: 1px solid rgba(47, 107, 255, 0.12);
-}
-.ms-now-dot {
- width: 6px;
- height: 6px;
- border-radius: 50%;
- background: var(--bd-green, #17b26a);
- box-shadow: 0 0 8px rgba(23, 178, 106, 0.8);
- animation: msPulse 1.6s ease-in-out infinite;
-}
-@keyframes msPulse {
- 0%, 100% { opacity: 1; transform: scale(1); }
- 50% { opacity: 0.4; transform: scale(0.75); }
-}
-.ms-now-name {
- font-size: 11px;
- font-weight: 500;
- color: var(--bd-sub, #7d8fa9);
- max-width: 160px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-/* 声音提示 */
-.ms-sound {
- display: inline-flex;
- align-items: center;
- gap: 6px;
- padding: 5px 11px;
- font-size: 11px;
- font-weight: 500;
- color: #4a5b76;
- background: rgba(255, 255, 255, 0.75);
- border: 1px solid rgba(47, 107, 255, 0.14);
- border-radius: 30px;
- cursor: pointer;
-}
-.ms-sound .appica-ico { color: var(--bd-blue, #2f6bff); }
+/* 播放控制已上移页眉(见 datascreen.css .bd-hd-media) */
/* 页脚 */
.ms-page .bd-strip { z-index: 10; position: relative; }
diff --git a/src/utils/mediaControl.js b/src/utils/mediaControl.js
new file mode 100644
index 0000000..690fa0e
--- /dev/null
+++ b/src/utils/mediaControl.js
@@ -0,0 +1,37 @@
+/* =========================================================
+ 媒体播放控制桥 —— 页眉控制区与媒体页之间的共享状态
+ · 媒体页把播放状态同步进来(paused/index/total/name/soundBlocked/loaded)
+ · 页眉控制区订阅状态渲染,并通过 mediaCommand() 下发指令
+ ========================================================= */
+
+let state = {
+ paused: false,
+ index: 0,
+ total: 0,
+ name: '',
+ soundBlocked: false,
+ loaded: false,
+};
+
+const listeners = new Set();
+
+export function setMediaUiState(patch) {
+ state = { ...state, ...patch };
+ listeners.forEach((fn) => fn(state));
+}
+
+export function getMediaUiState() {
+ return state;
+}
+
+/** 订阅状态,返回取消函数 */
+export function onMediaUiState(fn) {
+ listeners.add(fn);
+ fn(state);
+ return () => listeners.delete(fn);
+}
+
+/** 页眉控制区下发指令(媒体页监听 dpm:media-control 执行) */
+export function mediaCommand(action, params) {
+ window.dispatchEvent(new CustomEvent('dpm:media-control', { detail: { action, params } }));
+}