perf: 优化客户端媒体浏览器

This commit is contained in:
kuaifan 2024-12-02 20:50:10 +08:00
parent 8afc1db72f
commit 65e75f974d
3 changed files with 336 additions and 340 deletions

View File

@ -479,6 +479,7 @@ function createMediaWindow(args, type = 'image') {
height: args.height || 700, height: args.height || 700,
minWidth: 360, minWidth: 360,
minHeight: 360, minHeight: 360,
autoHideMenuBar: true,
webPreferences: { webPreferences: {
nodeIntegration: true, nodeIntegration: true,
contextIsolation: false, contextIsolation: false,

View File

@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Video</title> <title>Video</title>
<link rel="stylesheet" href="./plyr.css" /> <link rel="stylesheet" href="./plyr.css"/>
<style> <style>
html, body { html, body {
margin: 0; margin: 0;
@ -14,6 +14,7 @@
overflow: hidden; overflow: hidden;
user-select: none; user-select: none;
} }
#video-container { #video-container {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -21,346 +22,351 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.plyr { .plyr {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
video { video {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
/* 自定义播放器主题色 */ /* 自定义播放器主题色 */
:root { :root {
--plyr-color-main: #409eff; --plyr-color-main: #409eff;
} }
/* 黑色背景主题 */ /* 黑色背景主题 */
.plyr--video { .plyr--video {
background: #000; background: #000;
} }
.plyr__control--overlaid { .plyr__control--overlaid {
background: rgba(64, 158, 255, 0.8); background: rgba(64, 158, 255, 0.8);
} }
</style> </style>
</head> </head>
<body> <body>
<div id="video-container"> <div id="video-container">
<video id="player" playsinline controls> <video id="player" playsinline controls>
Your browser does not support the video tag. Your browser does not support the video tag.
</video> </video>
</div> </div>
<script src="./plyr.polyfilled.js"></script> <script src="./plyr.polyfilled.js"></script>
<script> <script>
const { ipcRenderer } = require('electron'); const {ipcRenderer} = require('electron');
// 多语言翻译 // 多语言翻译
const translations = { const translations = {
zh: { zh: {
speed: '播放速度', speed: '播放速度',
normal: '正常', normal: '正常',
quality: '视频质量', quality: '视频质量',
loop: '循环播放', loop: '循环播放',
play: '播放', play: '播放',
pause: '暂停', pause: '暂停',
played: '已播放', played: '已播放',
buffered: '已缓冲', buffered: '已缓冲',
currentTime: '当前时间', currentTime: '当前时间',
duration: '总时长', duration: '总时长',
volume: '音量', volume: '音量',
mute: '静音', mute: '静音',
unmute: '取消静音', unmute: '取消静音',
enableCaptions: '启用字幕', enableCaptions: '启用字幕',
disableCaptions: '禁用字幕', disableCaptions: '禁用字幕',
enterFullscreen: '进入全屏', enterFullscreen: '进入全屏',
exitFullscreen: '退出全屏', exitFullscreen: '退出全屏',
frameTitle: '视频播放器', frameTitle: '视频播放器',
captions: '字幕', captions: '字幕',
settings: '设置', settings: '设置',
menuBack: '返回上级菜单', menuBack: '返回上级菜单',
restart: '重新播放', restart: '重新播放',
rewind: '后退 {seektime} 秒', rewind: '后退 {seektime} 秒',
forward: '前进 {seektime} 秒' forward: '前进 {seektime} 秒'
}, },
'zh-CHT': { 'zh-CHT': {
speed: '播放速度', speed: '播放速度',
normal: '正常', normal: '正常',
quality: '視頻質量', quality: '視頻質量',
loop: '循環播放', loop: '循環播放',
play: '播放', play: '播放',
pause: '暫停', pause: '暫停',
played: '已播放', played: '已播放',
buffered: '已緩衝', buffered: '已緩衝',
currentTime: '當前時間', currentTime: '當前時間',
duration: '總時長', duration: '總時長',
volume: '音量', volume: '音量',
mute: '靜音', mute: '靜音',
unmute: '取消靜音', unmute: '取消靜音',
enableCaptions: '啟用字幕', enableCaptions: '啟用字幕',
disableCaptions: '禁用字幕', disableCaptions: '禁用字幕',
enterFullscreen: '進入全屏', enterFullscreen: '進入全屏',
exitFullscreen: '退出全屏', exitFullscreen: '退出全屏',
frameTitle: '視頻播放器', frameTitle: '視頻播放器',
captions: '字幕', captions: '字幕',
settings: '設置', settings: '設置',
menuBack: '返回上級菜單', menuBack: '返回上級菜單',
restart: '重新播放', restart: '重新播放',
rewind: '後退 {seektime} 秒', rewind: '後退 {seektime} 秒',
forward: '前進 {seektime} 秒' forward: '前進 {seektime} 秒'
}, },
en: { en: {
speed: 'Speed', speed: 'Speed',
normal: 'Normal', normal: 'Normal',
quality: 'Quality', quality: 'Quality',
loop: 'Loop', loop: 'Loop',
play: 'Play', play: 'Play',
pause: 'Pause', pause: 'Pause',
played: 'Played', played: 'Played',
buffered: 'Buffered', buffered: 'Buffered',
currentTime: 'Current time', currentTime: 'Current time',
duration: 'Duration', duration: 'Duration',
volume: 'Volume', volume: 'Volume',
mute: 'Mute', mute: 'Mute',
unmute: 'Unmute', unmute: 'Unmute',
enableCaptions: 'Enable captions', enableCaptions: 'Enable captions',
disableCaptions: 'Disable captions', disableCaptions: 'Disable captions',
enterFullscreen: 'Enter fullscreen', enterFullscreen: 'Enter fullscreen',
exitFullscreen: 'Exit fullscreen', exitFullscreen: 'Exit fullscreen',
frameTitle: 'Video player', frameTitle: 'Video player',
captions: 'Captions', captions: 'Captions',
settings: 'Settings', settings: 'Settings',
menuBack: 'Go back to previous menu', menuBack: 'Go back to previous menu',
restart: 'Restart', restart: 'Restart',
rewind: 'Rewind {seektime}s', rewind: 'Rewind {seektime}s',
forward: 'Forward {seektime}s' forward: 'Forward {seektime}s'
}, },
ja: { ja: {
speed: '再生速度', speed: '再生速度',
normal: '標準', normal: '標準',
quality: '画質', quality: '画質',
loop: 'ループ', loop: 'ループ',
play: '再生', play: '再生',
pause: '一時停止', pause: '一時停止',
played: '再生済み', played: '再生済み',
buffered: 'バッファリング済み', buffered: 'バッファリング済み',
currentTime: '現在の時間', currentTime: '現在の時間',
duration: '合計時間', duration: '合計時間',
volume: '音量', volume: '音量',
mute: 'ミュート', mute: 'ミュート',
unmute: 'ミュート解除', unmute: 'ミュート解除',
enableCaptions: '字幕をオン', enableCaptions: '字幕をオン',
disableCaptions: '字幕をオフ', disableCaptions: '字幕をオフ',
enterFullscreen: '全画面表示', enterFullscreen: '全画面表示',
exitFullscreen: '全画面解除', exitFullscreen: '全画面解除',
frameTitle: 'ビデオプレーヤー', frameTitle: 'ビデオプレーヤー',
captions: '字幕', captions: '字幕',
settings: '設定', settings: '設定',
menuBack: '前のメニューに戻る', menuBack: '前のメニューに戻る',
restart: '最初から再生', restart: '最初から再生',
rewind: '{seektime}秒戻る', rewind: '{seektime}秒戻る',
forward: '{seektime}秒進む' forward: '{seektime}秒進む'
}, },
ko: { ko: {
speed: '재생 속도', speed: '재생 속도',
normal: '보통', normal: '보통',
quality: '품질', quality: '품질',
loop: '반복', loop: '반복',
play: '재생', play: '재생',
pause: '일시정지', pause: '일시정지',
played: '재생됨', played: '재생됨',
buffered: '버퍼링됨', buffered: '버퍼링됨',
currentTime: '현재 시간', currentTime: '현재 시간',
duration: '총 시간', duration: '총 시간',
volume: '볼륨', volume: '볼륨',
mute: '음소거', mute: '음소거',
unmute: '음소거 해제', unmute: '음소거 해제',
enableCaptions: '자막 켜기', enableCaptions: '자막 켜기',
disableCaptions: '자막 끄기', disableCaptions: '자막 끄기',
enterFullscreen: '전체화면', enterFullscreen: '전체화면',
exitFullscreen: '전체화면 나가기', exitFullscreen: '전체화면 나가기',
frameTitle: '비디오 플레이어', frameTitle: '비디오 플레이어',
captions: '자막', captions: '자막',
settings: '설정', settings: '설정',
menuBack: '이전 메뉴로 돌아가기', menuBack: '이전 메뉴로 돌아가기',
restart: '다시 시작', restart: '다시 시작',
rewind: '{seektime}초 뒤로', rewind: '{seektime}초 뒤로',
forward: '{seektime}초 앞으로' forward: '{seektime}초 앞으로'
}, },
de: { de: {
speed: 'Geschwindigkeit', speed: 'Geschwindigkeit',
normal: 'Normal', normal: 'Normal',
quality: 'Qualität', quality: 'Qualität',
loop: 'Wiederholen', loop: 'Wiederholen',
play: 'Abspielen', play: 'Abspielen',
pause: 'Pause', pause: 'Pause',
played: 'Gespielt', played: 'Gespielt',
buffered: 'Gepuffert', buffered: 'Gepuffert',
currentTime: 'Aktuelle Zeit', currentTime: 'Aktuelle Zeit',
duration: 'Gesamtzeit', duration: 'Gesamtzeit',
volume: 'Lautstärke', volume: 'Lautstärke',
mute: 'Stummschalten', mute: 'Stummschalten',
unmute: 'Ton einschalten', unmute: 'Ton einschalten',
enableCaptions: 'Untertitel aktivieren', enableCaptions: 'Untertitel aktivieren',
disableCaptions: 'Untertitel deaktivieren', disableCaptions: 'Untertitel deaktivieren',
enterFullscreen: 'Vollbild', enterFullscreen: 'Vollbild',
exitFullscreen: 'Vollbild beenden', exitFullscreen: 'Vollbild beenden',
frameTitle: 'Video-Player', frameTitle: 'Video-Player',
captions: 'Untertitel', captions: 'Untertitel',
settings: 'Einstellungen', settings: 'Einstellungen',
menuBack: 'Zurück zum vorherigen Menü', menuBack: 'Zurück zum vorherigen Menü',
restart: 'Neu starten', restart: 'Neu starten',
rewind: '{seektime}s zurück', rewind: '{seektime}s zurück',
forward: '{seektime}s vorwärts' forward: '{seektime}s vorwärts'
}, },
fr: { fr: {
speed: 'Vitesse', speed: 'Vitesse',
normal: 'Normale', normal: 'Normale',
quality: 'Qualité', quality: 'Qualité',
loop: 'Boucle', loop: 'Boucle',
play: 'Lecture', play: 'Lecture',
pause: 'Pause', pause: 'Pause',
played: 'Lu', played: 'Lu',
buffered: 'Tamponné', buffered: 'Tamponné',
currentTime: 'Temps actuel', currentTime: 'Temps actuel',
duration: 'Durée', duration: 'Durée',
volume: 'Volume', volume: 'Volume',
mute: 'Muet', mute: 'Muet',
unmute: 'Son activé', unmute: 'Son activé',
enableCaptions: 'Activer les sous-titres', enableCaptions: 'Activer les sous-titres',
disableCaptions: 'Désactiver les sous-titres', disableCaptions: 'Désactiver les sous-titres',
enterFullscreen: 'Plein écran', enterFullscreen: 'Plein écran',
exitFullscreen: 'Quitter le plein écran', exitFullscreen: 'Quitter le plein écran',
frameTitle: 'Lecteur vidéo', frameTitle: 'Lecteur vidéo',
captions: 'Sous-titres', captions: 'Sous-titres',
settings: 'Paramètres', settings: 'Paramètres',
menuBack: 'Retour au menu précédent', menuBack: 'Retour au menu précédent',
restart: 'Redémarrer', restart: 'Redémarrer',
rewind: 'Reculer de {seektime}s', rewind: 'Reculer de {seektime}s',
forward: 'Avancer de {seektime}s' forward: 'Avancer de {seektime}s'
}, },
id: { id: {
speed: 'Kecepatan', speed: 'Kecepatan',
normal: 'Normal', normal: 'Normal',
quality: 'Kualitas', quality: 'Kualitas',
loop: 'Putar Ulang', loop: 'Putar Ulang',
play: 'Putar', play: 'Putar',
pause: 'Jeda', pause: 'Jeda',
played: 'Telah Diputar', played: 'Telah Diputar',
buffered: 'Telah Buffer', buffered: 'Telah Buffer',
currentTime: 'Waktu Saat Ini', currentTime: 'Waktu Saat Ini',
duration: 'Durasi', duration: 'Durasi',
volume: 'Volume', volume: 'Volume',
mute: 'Bisukan', mute: 'Bisukan',
unmute: 'Suarakan', unmute: 'Suarakan',
enableCaptions: 'Aktifkan Teks', enableCaptions: 'Aktifkan Teks',
disableCaptions: 'Nonaktifkan Teks', disableCaptions: 'Nonaktifkan Teks',
enterFullscreen: 'Layar Penuh', enterFullscreen: 'Layar Penuh',
exitFullscreen: 'Keluar Layar Penuh', exitFullscreen: 'Keluar Layar Penuh',
frameTitle: 'Pemutar Video', frameTitle: 'Pemutar Video',
captions: 'Teks', captions: 'Teks',
settings: 'Pengaturan', settings: 'Pengaturan',
menuBack: 'Kembali ke menu sebelumnya', menuBack: 'Kembali ke menu sebelumnya',
restart: 'Mulai Ulang', restart: 'Mulai Ulang',
rewind: 'Mundur {seektime} detik', rewind: 'Mundur {seektime} detik',
forward: 'Maju {seektime} detik' forward: 'Maju {seektime} detik'
}, },
ru: { ru: {
speed: 'Скорость', speed: 'Скорость',
normal: 'Нормальная', normal: 'Нормальная',
quality: 'Качество', quality: 'Качество',
loop: 'Повтор', loop: 'Повтор',
play: 'Воспроизвести', play: 'Воспроизвести',
pause: 'Пауза', pause: 'Пауза',
played: 'Воспроизведено', played: 'Воспроизведено',
buffered: 'Буферизовано', buffered: 'Буферизовано',
currentTime: 'Текущее время', currentTime: 'Текущее время',
duration: 'Продолжительность', duration: 'Продолжительность',
volume: 'Громкость', volume: 'Громкость',
mute: 'Без звука', mute: 'Без звука',
unmute: 'Со звуком', unmute: 'Со звуком',
enableCaptions: 'Включить субтитры', enableCaptions: 'Включить субтитры',
disableCaptions: 'Отключить субтитры', disableCaptions: 'Отключить субтитры',
enterFullscreen: 'Полноэкранный режим', enterFullscreen: 'Полноэкранный режим',
exitFullscreen: 'Выйти из полноэкранного режима', exitFullscreen: 'Выйти из полноэкранного режима',
frameTitle: 'Видеоплеер', frameTitle: 'Видеоплеер',
captions: 'Субтитры', captions: 'Субтитры',
settings: 'Настройки', settings: 'Настройки',
menuBack: 'Вернуться в предыдущее меню', menuBack: 'Вернуться в предыдущее меню',
restart: 'Перезапустить', restart: 'Перезапустить',
rewind: 'Назад на {seektime} сек', rewind: 'Назад на {seektime} сек',
forward: 'Вперед на {seektime} сек' forward: 'Вперед на {seektime} сек'
} }
}; };
// 标题翻译 // 标题翻译
const titleTranslations = { const titleTranslations = {
zh: '视频播放器', zh: '视频播放器',
'zh-CHT': '視頻播放器', 'zh-CHT': '視頻播放器',
en: 'Video Player', en: 'Video Player',
ko: '비디오 플레이어', ko: '비디오 플레이어',
ja: 'ビデオプレーヤー', ja: 'ビデオプレーヤー',
de: 'Video-Player', de: 'Video-Player',
fr: 'Lecteur Vidéo', fr: 'Lecteur Vidéo',
id: 'Pemutar Video', id: 'Pemutar Video',
ru: 'Видеоплеер' ru: 'Видеоплеер'
}; };
let player = null; let player = null;
// 接收主进程传来的视频路径 // 接收主进程传来的视频路径
ipcRenderer.on('load-media', (event, args) => { ipcRenderer.on('load-media', (event, args) => {
const videoElement = document.querySelector('video'); const videoElement = document.querySelector('video');
videoElement.src = args.video; videoElement.src = args.video;
// 销毁旧的播放器实例 // 销毁旧的播放器实例
if (player) { if (player) {
player.destroy(); player.destroy();
} }
// 设置语言 // 设置语言
const currentLang = args.lang || 'en'; const currentLang = args.lang || 'en';
// 创建新的播放器实例 // 创建新的播放器实例
player = new Plyr('#player', { player = new Plyr('#player', {
controls: [ controls: [
'play-large', // 大播放按钮 'play-large', // 大播放按钮
'play', // 播放/暂停 'play', // 播放/暂停
'progress', // 进度条 'progress', // 进度条
'current-time', // 当前时间 'current-time', // 当前时间
'duration', // 总时长 'duration', // 总时长
'mute', // 静音 'mute', // 静音
'volume', // 音量 'volume', // 音量
'settings', // 设置 'settings', // 设置
'fullscreen' // 全屏 'fullscreen' // 全屏
], ],
settings: ['captions', 'quality', 'speed', 'loop'], // 设置菜单选项 settings: ['captions', 'quality', 'speed', 'loop'], // 设置菜单选项
speed: { selected: 1, options: [0.5, 0.75, 1, 1.25, 1.5, 2] }, // 播放速度选项 speed: {selected: 1, options: [0.5, 0.75, 1, 1.25, 1.5, 2]}, // 播放速度选项
keyboard: { focused: true, global: true }, // 启用键盘快捷键 keyboard: {focused: true, global: true}, // 启用键盘快捷键
tooltips: { controls: true, seek: true }, // 显示工具提示 tooltips: {controls: true, seek: true}, // 显示工具提示
hideControls: true, // 自动隐藏控制栏 hideControls: true, // 自动隐藏控制栏
i18n: translations[currentLang] || translations['en'] // 设置语言 i18n: translations[currentLang] || translations['en'] // 设置语言
});
// 错误处理
player.on('error', (event) => {
console.error('Player error:', event);
});
// 更新网页标题
document.title = args.title || titleTranslations[currentLang] || titleTranslations['en'];
// 自动播放
player.play().catch(e => {
console.log('Auto-play failed:', e);
});
}); });
// 快捷键处理 // 错误处理
document.addEventListener('keydown', (e) => { player.on('error', (event) => {
// ESC 键关闭窗口 console.error('Player error:', event);
if (e.key === 'Escape' && player && !player.fullscreen.active) {
window.close();
}
}); });
</script>
// 更新网页标题
document.title = args.title || titleTranslations[currentLang] || titleTranslations['en'];
// 自动播放
player.play().catch(e => {
console.log('Auto-play failed:', e);
});
});
// 快捷键处理
document.addEventListener('keydown', (e) => {
// ESC 键关闭窗口
if (e.key === 'Escape' && player && !player.fullscreen.active) {
window.close();
}
});
</script>
</body> </body>
</html> </html>

View File

@ -27,36 +27,29 @@
.viewer-close { .viewer-close {
display: none; display: none;
} }
/* 加载动画 */
.loading {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 40px;
height: 40px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
display: none;
}
@keyframes spin {
to { transform: translate(-50%, -50%) rotate(360deg); }
}
</style> </style>
</head> </head>
<body> <body>
<div id="image-container"></div> <div id="image-container"></div>
<div class="loading"></div>
<script src="./viewer.min.js"></script> <script src="./viewer.min.js"></script>
<script> <script>
const {ipcRenderer} = require('electron'); const {ipcRenderer} = require('electron');
let viewer = null; let viewer = null;
const thumbnailUrl = (url) => {
const crops = {
ratio: 3,
percentage: '320x0'
}
url = `${url}`
.replace(/_thumb\.(png|jpg|jpeg)$/, '')
.replace(/\/crop\/([^\/]+)$/, '')
return url + "/crop/" + Object.keys(crops).map(key => {
return `${key}:${crops[key]}`
}).join(",")
}
// 标题翻译 // 标题翻译
const titleTranslations = { const titleTranslations = {
zh: '图片查看器', zh: '图片查看器',
@ -73,9 +66,6 @@
// 接收主进程发送的图片数据 // 接收主进程发送的图片数据
ipcRenderer.on('load-media', (event, args) => { ipcRenderer.on('load-media', (event, args) => {
const container = document.getElementById('image-container'); const container = document.getElementById('image-container');
const loading = document.querySelector('.loading');
loading.style.display = 'block';
container.innerHTML = ''; // 清空容器 container.innerHTML = ''; // 清空容器
// 更新网页标题 // 更新网页标题
@ -83,9 +73,10 @@
document.title = args.title || titleTranslations[currentLang] || titleTranslations['en']; document.title = args.title || titleTranslations[currentLang] || titleTranslations['en'];
// 创建图片元素 // 创建图片元素
args.images.forEach((src, index) => { args.images.forEach(src => {
const img = document.createElement('img'); const img = document.createElement('img');
img.src = src; img.src = thumbnailUrl(src);
img.setAttribute('data-original', src);
container.appendChild(img); container.appendChild(img);
}); });
@ -119,7 +110,7 @@
}, },
// 是否显示缩略图导航栏 // 是否显示缩略图导航栏
navbar: true, navbar: 2,
// 是否显示工具提示 // 是否显示工具提示
tooltip: true, tooltip: true,
@ -155,14 +146,12 @@
// 'static':点击背景不会关闭查看器 // 'static':点击背景不会关闭查看器
backdrop: 'static', backdrop: 'static',
// 使用url属性来加载原始图片
url: 'data-original',
// 初始显示第几张图片从0开始计数 // 初始显示第几张图片从0开始计数
initialViewIndex: args.currentIndex || 0, initialViewIndex: args.currentIndex || 0,
// 图片加载完成后的回调函数
viewed() {
loading.style.display = 'none';
},
// 查看器隐藏时的回调函数 // 查看器隐藏时的回调函数
hidden() { hidden() {
window.close(); window.close();