今天玩崩溃了,大家也知道皮皮是代码小白,很多情况下都是问Ai。所以今天为了让文章内容页支持调用我所谓的皮皮音乐盒页面的歌曲折腾了半天快3个小时。在折腾的中途中有几个版本能获取到歌曲的封面了,我没保存没备份。不知道后面怎么搞的,搞来搞去就不支持不显示歌曲封面了。现在在奔溃的边缘。所以在线求救懂代码的大佬们。东哥,钟小姐,李日志李哥等等各位大佬快来帮解决下歌曲列表获取不到封面问题。下面我贴出相关代码!快来9958~~
皮皮音乐盒代码:
<?php
/**
* 皮皮音乐盒 V2.3 (个人版)
* @package custom
*/
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
$this->setPingback(false);
$musicRootDir = __TYPECHO_ROOT_DIR__ . '/usr/uploads/music_files/';
$lyricRootDir = __TYPECHO_ROOT_DIR__ . '/usr/uploads/music_lyrics/';
$imageRootDir = __TYPECHO_ROOT_DIR__ . '/usr/uploads/music_images/';
$allowedMusicExt = ['mp3'];
$allowedLyricExt = ['lrc'];
$allowedImageExt = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
$MAX_UPLOAD_SIZE = 15 * 1024 * 1024;
function initDir($dir) {
!is_dir($dir) && mkdir($dir, 0755, true);
}
initDir($musicRootDir);
initDir($lyricRootDir);
initDir($imageRootDir);
$currentMonthFolder = date('Ym');
$musicDir = $musicRootDir . $currentMonthFolder . '/';
$lyricDir = $lyricRootDir . $currentMonthFolder . '/';
$imageDir = $imageRootDir . $currentMonthFolder . '/';
initDir($musicDir);
initDir($lyricDir);
initDir($imageDir);
function htmlEscape($str) {
return htmlspecialchars($str ?? '', ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
function getSafeFileUrl($typechoObj, $filePath) {
$siteUrl = '';
if ($typechoObj && isset($typechoObj->options) && $typechoObj->options->siteUrl) {
$siteUrl = rtrim($typechoObj->options->siteUrl, '/');
} else if (defined('__TYPECHO_SITE_URL__')) {
$siteUrl = rtrim(__TYPECHO_SITE_URL__, '/');
} else {
$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'] ?? '';
$siteUrl = rtrim("$protocol://$host", '/');
}
$relativePath = str_replace(__TYPECHO_ROOT_DIR__, '', $filePath);
$dir = dirname($relativePath);
$file = basename($relativePath);
return $siteUrl . $dir . '/' . rawurlencode($file);
}
function formatFileSize($bytes) {
$units = ['B', 'KB', 'MB', 'GB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
return round($bytes / (1 << (10 * $pow)), 2) . ' ' . $units[$pow];
}
function isAdmin() {
$user = Typecho_Widget::widget('Widget_User');
return $user->hasLogin() && $user->pass('administrator');
}
$isAdmin = isAdmin();
// 删除文件处理(无调试版 · 干净稳定)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_POST['action'] === 'delete') {
require_once __TYPECHO_ROOT_DIR__ . '/var/Typecho/Common.php';
if (!$isAdmin) {
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['success' => false, 'msg' => '无删除权限']);
exit;
}
$file = htmlEscape($_POST['file'] ?? '');
if (empty($file)) {
echo json_encode(['success' => false, 'msg' => '参数错误:文件名为空']);
exit;
}
$safeFile = basename($file);
$nameWithoutExt = pathinfo($safeFile, PATHINFO_FILENAME);
$foundMusic = '';
$foundFolder = '';
// 遍历所有年月文件夹
if (is_dir($musicRootDir)) {
$dirs = scandir($musicRootDir);
foreach ($dirs as $dir) {
if ($dir === '.' || $dir === '..' || !is_dir($musicRootDir . $dir)) continue;
$candidate = $musicRootDir . $dir . '/' . $safeFile;
if (file_exists($candidate) && is_file($candidate)) {
$foundMusic = $candidate;
$foundFolder = $dir;
break;
}
}
}
// 没找到文件
if (empty($foundMusic) || !file_exists($foundMusic)) {
echo json_encode([
'success' => false,
'msg' => '文件不存在'
]);
exit;
}
// 权限检查
if (!is_writable($foundMusic)) {
echo json_encode([
'success' => false,
'msg' => '删除失败:权限不足'
]);
exit;
}
// 匹配歌词、封面
$lyricFile = $lyricRootDir . $foundFolder . '/' . $nameWithoutExt . '.lrc';
$imageFile = '';
foreach ($allowedImageExt as $ext) {
$imgCandidate = $imageRootDir . $foundFolder . '/' . $nameWithoutExt . '.' . $ext;
if (file_exists($imgCandidate) && is_file($imgCandidate)) {
$imageFile = $imgCandidate;
break;
}
}
// 执行删除
$musicDel = @unlink($foundMusic);
$lyricDel = (!empty($lyricFile) && file_exists($lyricFile)) ? @unlink($lyricFile) : true;
$imageDel = (!empty($imageFile) && file_exists($imageFile)) ? @unlink($imageFile) : true;
if ($musicDel) {
$msg = '删除成功';
if (!$lyricDel) $msg .= '(歌词删失败)';
if (!$imageDel) $msg .= '(封面删失败)';
echo json_encode(['success' => true, 'msg' => $msg]);
} else {
echo json_encode([
'success' => false,
'msg' => '删除失败:系统错误'
]);
}
exit;
}
// 上传文件处理
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['upload_files'])) {
if (!$isAdmin) {
ob_end_clean();
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['success' => false, 'msg' => '无上传权限']);
exit;
}
ob_end_clean();
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-cache, no-store, must-revalidate');
header('Pragma: no-cache');
header('Expires: 0');
$successCount = 0;
$errorMsg = '';
foreach ($_FILES['upload_files']['name'] as $key => $name) {
$tmpName = $_FILES['upload_files']['tmp_name'][$key];
$size = $_FILES['upload_files']['size'][$key];
$error = $_FILES['upload_files']['error'][$key];
if ($error !== UPLOAD_ERR_OK) {
$errorMsg .= "文件 {$name} 上传失败:错误码 {$error}\n";
continue;
}
if ($size > $MAX_UPLOAD_SIZE) {
$errorMsg .= "文件 {$name} 超过15M限制\n";
continue;
}
$ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
$allAllowedExt = array_merge($allowedMusicExt, $allowedLyricExt, $allowedImageExt);
if (!in_array($ext, $allAllowedExt)) {
$errorMsg .= "文件 {$name} 格式不支持\n";
continue;
}
if (in_array($ext, $allowedMusicExt)) {
$fileType = 'music';
$targetDir = $musicDir;
} elseif (in_array($ext, $allowedLyricExt)) {
$fileType = 'lyric';
$targetDir = $lyricDir;
} else {
$fileType = 'image';
$targetDir = $imageDir;
}
$safeName = basename($name);
$targetFile = $targetDir . $safeName;
if (file_exists($targetFile)) {
unlink($targetFile);
if ($fileType === 'music') {
$lyricTargetFile = $targetDir . pathinfo($name, PATHINFO_FILENAME) . '.lrc';
file_exists($lyricTargetFile) && unlink($lyricTargetFile);
foreach ($allowedImageExt as $imgExt) {
$imageTargetFile = $imageDir . pathinfo($name, PATHINFO_FILENAME) . '.' . $imgExt;
file_exists($imageTargetFile) && unlink($imageTargetFile);
}
}
}
if (move_uploaded_file($tmpName, $targetFile)) {
$successCount++;
} else {
$errorMsg .= "文件《 {$name} 》保存失败\n";
}
}
$response = [
'success' => $successCount > 0,
'msg' => $successCount > 0
? "成功上传 {$successCount} 个文件!" . ($errorMsg ? "\n错误信息:{$errorMsg}" : '')
: ($errorMsg ?: '上传失败')
];
echo json_encode($response, JSON_UNESCAPED_UNICODE);
exit;
}
// 获取音乐列表函数
function getMusicList($typechoObj, $musicRootDir, $lyricRootDir, $imageRootDir, $allowedMusicExt, $allowedImageExt) {
$musicList = [];
$id = 1;
if (!is_dir($musicRootDir) || !is_readable($musicRootDir)) return $musicList;
$dirs = scandir($musicRootDir);
foreach ($dirs as $dir) {
if ($dir === '.' || $dir === '..' || !is_dir($musicRootDir . $dir)) continue;
$musicDir = $musicRootDir . $dir . '/';
if ($dh = opendir($musicDir)) {
while (($file = readdir($dh)) !== false) {
if ($file === '.' || $file === '..') continue;
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
if (!in_array($ext, $allowedMusicExt)) continue;
$filePath = $musicDir . $file;
$fileUrl = getSafeFileUrl($typechoObj, $filePath);
$fileName = pathinfo($file, PATHINFO_FILENAME);
// ======================
// 全局搜索歌词(跨文件夹匹配)
// ======================
$lyricUrl = '';
if (is_dir($lyricRootDir)) {
$lyricDirs = scandir($lyricRootDir);
foreach ($lyricDirs as $lyrDir) {
if ($lyrDir === '.' || $lyrDir === '..' || !is_dir($lyricRootDir . $lyrDir)) continue;
$findLrc = $lyricRootDir . $lyrDir . '/' . $fileName . '.lrc';
if (file_exists($findLrc)) {
$lyricUrl = getSafeFileUrl($typechoObj, $findLrc);
break;
}
}
}
// ======================
// 全局搜索封面(跨文件夹匹配)
// ======================
$imageUrl = '';
if (is_dir($imageRootDir)) {
$imgDirs = scandir($imageRootDir);
foreach ($imgDirs as $imgDir) {
if ($imgDir === '.' || $imgDir === '..' || !is_dir($imageRootDir . $imgDir)) continue;
foreach ($allowedImageExt as $imgExt) {
$findImg = $imageRootDir . $imgDir . '/' . $fileName . '.' . $imgExt;
if (file_exists($findImg)) {
$imageUrl = getSafeFileUrl($typechoObj, $findImg);
break 2;
}
}
}
}
$musicList[] = [
'id' => $id++,
'name' => $fileName,
'file' => $file,
'url' => $fileUrl,
'size' => formatFileSize(filesize($filePath)),
'lyricUrl' => $lyricUrl,
'imageUrl' => $imageUrl,
'folder' => $dir
];
}
closedir($dh);
}
}
return $musicList;
}
$fullMusicList = getMusicList($this, $musicRootDir, $lyricRootDir, $imageRootDir, $allowedMusicExt, $allowedImageExt);
$this->need('header.php');
?>
<style>
/* 音乐播放波形动画 - 播放中显示 */
.pipi-music .music-wave-animate {
display: inline-flex;
align-items: center;
gap: 2px;
height: 14px;
margin-left: 6px;
vertical-align: middle;
}
.pipi-music .music-wave-animate span {
width: 2px;
background: #ffffff;
background: #FA8072;
border-radius: 1px;
animation: wave-animate 0.8s infinite ease-in-out;
}
.pipi-music .music-wave-animate span:nth-child(1) { animation-delay: -0.7s; height: 5px; }
.pipi-music .music-wave-animate span:nth-child(2) { animation-delay: -0.5s; height: 10px; }
.pipi-music .music-wave-animate span:nth-child(3) { animation-delay: -0.3s; height: 7px; }
@keyframes wave-animate {
0%,100% { transform: scaleY(0.4); }
50% { transform: scaleY(1); }
}
/* 默认隐藏波形 → 只有播放中的歌曲才显示 */
.pipi-music .music-wave-animate { display: none; }
.pipi-music .music-item.active .music-wave-animate { display: inline-flex; }
/* ===== 全局样式 ===== */
.pipi-music {
max-width: 1200px;
margin: 0 auto;
padding: 20px 0;
font-family: "Microsoft Yahei", sans-serif;
}
.pipi-music .sm-mainContent {
display: flex;
flex-wrap: wrap;
gap: 25px;
padding: 0 5px;
}
.pipi-music .sm-innermainContent {
flex: 1;
min-width: 300px;
}
.pipi-music .music-title {
text-align: center;
color: var(--color-huise);
font-size: var(--font-size-2xl);
font-weight: bold;
margin: 15px 0 30px;
}
.pipi-music .search-box {
max-width: 700px;
margin: 0 auto 25px;
display: flex;
gap: 8px;
padding: 0 10px;
}
.pipi-music .search-input {
flex: 1;
height: 40px;
padding: 0 12px;
border: 1px solid var(--bj-cifuse);
border-radius: 4px;
font-size: var(--font-size-base);
color: var(--color-chunhei);
outline: none;
transition: border-color 0.2s;
background-color: var(--bj-zhuse);
}
.pipi-music .search-input:focus {
border-color: var(--bj-fuse);
}
.admin-only {
display: <?php echo $isAdmin ? 'flex' : 'none'; ?> !important;
}
/* ===== 图标通用样式 ===== */
.pipi-music .icon-svg {
display: inline-block;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
flex-shrink: 0;
}
.pipi-music .icon-svg.lg {
width: 40px !important;
height: 40px !important;
}
.pipi-music .icon-svg.md {
width: 25px !important;
height: 25px !important;
}
.pipi-music .icon-svg.sm {
width: 18px;
height: 18px;
}
/* 原有图标 */
.pipi-music .icon-play { background-image: url('/usr/themes/dage/bai/img/bofangqi/播放.svg'); }
.pipi-music .icon-pause { background-image: url('/usr/themes/dage/bai/img/bofangqi/暂停.svg'); }
.pipi-music .icon-prev { background-image: url('/usr/themes/dage/bai/img/bofangqi/上一曲.svg'); }
.pipi-music .icon-next { background-image: url('/usr/themes/dage/bai/img/bofangqi/下一曲.svg'); }
.pipi-music .icon-list { background-image: url('/usr/themes/dage/bai/img/bofangqi/歌曲列表.svg'); }
.pipi-music .icon-collect { background-image: url('/usr/themes/dage/bai/img/bofangqi/收藏.svg'); }
.pipi-music .icon-volume { background-image: url('/usr/themes/dage/bai/img/bofangqi/音量.svg'); }
.pipi-music .icon-volume-mute { background-image: url('/usr/themes/dage/bai/img/bofangqi/静音.svg'); }
.pipi-music .icon-collect-normal { background-image: url('/usr/themes/dage/bai/img/bofangqi/未收藏.svg'); }
.pipi-music .icon-collect-selected { background-image: url('/usr/themes/dage/bai/img/bofangqi/收藏.svg'); }
.pipi-music .icon-delete { background-image: url('/usr/themes/dage/bai/img/bofangqi/删除.svg'); }
.pipi-music .icon-search { background-image: url('/usr/themes/dage/bai/img/bofangqi/搜索.svg'); }
.pipi-music .icon-upload { background-image: url('/usr/themes/dage/bai/img/bofangqi/上传.svg'); }
/* 新增的歌词和头像图标 */
.pipi-music .icon-lyric {
background-image: url('/usr/themes/dage/bai/img/bofangqi/歌词.svg');
}
.pipi-music .icon-image {
background-image: url('/usr/themes/dage/bai/img/bofangqi/头像.svg');
}
/* ===== 播放器区域 ===== */
.pipi-music .player-box {
background-color: var(--bj-zhuse);
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
margin: 0 auto 15px;
max-width: 700px;
border: 1px solid var(--bj-cifuse);
}
.pipi-music .current-playing {
text-align: center;
margin: 0 0 20px;
}
.pipi-music .current-playing-name {
color: var(--color-chunhei);
font-size: var(--font-size-lg);
}
.pipi-music .player-controls {
display: flex;
justify-content: center;
align-items: center;
gap: 2px;
margin: 0 0 20px;
flex-wrap: wrap;
}
@media screen and (max-width: 768px) {
.pipi-music .player-controls {
gap: 1px !important;
flex-wrap: nowrap !important;
}
}
.pipi-music .mode-btn {
padding: 0;
border: none;
background-color: transparent !important;
color: var(--bj-fuse);
cursor: pointer;
width: 55px !important;
height: 55px !important;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: color 0.2s;
}
.pipi-music .mode-btn.active {
color: var(--bj-lvse) !important;
}
.pipi-music .mode-btn:hover:not(.active) {
color: #4CAF50 !important;
}
.pipi-music .control-btn {
color: var(--bj-fuse);
border: none;
background-color: transparent !important;
cursor: pointer;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: color 0.2s;
width: 75px !important;
height: 75px !important;
}
.pipi-music .control-btn.small {
width: 55px !important;
height: 55px !important;
}
.pipi-music .control-btn:disabled,
.pipi-music .control-btn.disabled {
color: #ccc !important;
cursor: not-allowed !important;
opacity: 0.6;
}
.pipi-music .control-btn:hover:not(:disabled) {
color: var(--bj-lvse) !important;
}
.pipi-music .progress-volume-group {
width: 100%;
max-width: 700px;
margin: 0 auto;
}
.pipi-music .progress-volume-wrap {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
}
.pipi-music .progress-area {
flex: 1;
height: 8px;
}
.pipi-music .progress-bar {
width: 100%;
height: 100%;
background-color: #eee;
border-radius: 4px;
cursor: pointer;
position: relative;
}
.pipi-music .progress-fill {
height: 100%;
width: 0%;
background-color: var(--bj-fuse);
border-radius: 4px;
}
.pipi-music .volume-area {
display: flex;
align-items: center;
gap: 1px;
width: 100px;
flex-shrink: 0;
}
.pipi-music .volume-area .btn {
border: none;
background-color: transparent;
color: var(--bj-fuse);
cursor: pointer;
width: 25px;
height: 25px;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
}
.pipi-music .volume-bar {
width: 70px;
height: 8px;
background-color: #eee;
border-radius: 4px;
cursor: pointer;
position: relative;
}
.pipi-music .volume-fill {
height: 100%;
width: 80%;
background-color: var(--bj-fuse);
border-radius: 4px;
}
.pipi-music .time-display {
width: 100%;
text-align: center;
font-size: var(--font-size-sm);
color: var(--color-huise);
margin: 10px 0 0;
}
.pipi-music .lyric-container {
height: 170px;
padding: 10px 10px;
text-align: center;
overflow: hidden;
position: relative;
border-radius: 6px;
background-color:#787878;
margin-top: 15px;
}
.pipi-music .music-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
opacity: 0.2;
z-index: 1;
}
.pipi-music .lyric-list {
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 100%;
padding: 15px 0;
transition: top 0.3s ease-out;
will-change: top;
z-index: 2;
}
.pipi-music .lyric-line {
color: #fff;
font-size: var(--font-size-base);
line-height: 28px;
margin: 0 auto;
width: fit-content;
padding: 0 10px;
opacity: 0.7;
transition: all 0.25s ease;
transform: scale(1);
z-index: 2;
position: relative;
}
.pipi-music .lyric-line.active {
color: #A6D745;
background: linear-gradient(135deg, #A6D745, #C3E68C);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-size: var(--font-size-lg);
font-weight: 600;
opacity: 1;
border-radius: 4px;
transform: scale(1.35);
text-shadow: 0 0 12px rgba(166, 215, 69, 0.8);
filter: none;
}
.pipi-music .lyric-line:not(.active) {
filter: blur(0.5px);
}
.pipi-music .empty-tip {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #fff;
font-size: var(--font-size-sm);
z-index: 2;
}
.pipi-music .music-list .empty-tip {
position: static;
transform: none;
text-align: center;
padding: 20px 0;
color: #dc3545;
font-size: var(--font-size-lg);
background: none;
}
/* ===== 歌曲列表 ===== */
/* ===== 歌曲列表 ===== */
.pipi-music .music-list {
max-height: none; /* 👈 去掉高度限制 */
overflow-y: visible; /* 👈 关闭滚动条 */
border: 1px solid var(--bj-cifuse);
background-color: var(--bj-zhuse);
padding: 10px 0;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
margin: 0 auto 10px;
max-width: 700px;
scroll-margin-top: 10px;
}
.pipi-music .music-item {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 15px;
margin: 0;
width: 100%;
font-size: var(--font-size-base);
position: relative;
z-index: 1;
cursor: pointer;
border-radius: 0;
line-height: 1.5;
background-color: transparent;
transition: background-color 0.2s;
}
.pipi-music .music-item.active {
border: none;
padding: 8px 15px;
margin: 0;
background-color: var(--bj-cifuse);
color: var(--bj-fuse);
}
.pipi-music .music-num {
color: var(--color-huise);
font-size: var(--font-size-sm);
flex-shrink: 0;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
}
/* 歌曲名称容器:占满剩余宽度,内部使用行内流布局 */
.pipi-music .music-name {
flex: 1; /* 占据父级剩余空间 */
min-width: 0; /* 防止 flex 溢出 */
font-size: var(--font-size-base);
font-weight: 500;
color: var(--color-chunhei);
word-break: break-word; /* 长单词自动断行 */
line-height: 1.5;
/* 移除 display:flex、align-items、flex-wrap、gap 等属性 */
}
/* 歌词/封面图标容器:行内弹性布局,跟随文本末尾 */
.pipi-music .music-icons {
display: inline-flex;
align-items: center;
gap: 4px; /* 多个图标之间的间距 */
margin-left: 6px; /* 与歌曲名称的间距 */
vertical-align: middle; /* 垂直居中对齐 */
white-space: nowrap; /* 防止图标内部换行 */
flex-shrink: 0;
}
.pipi-music .text-btn {
width: auto;
height: 20px;
flex-shrink: 0;
cursor: pointer;
display: flex !important;
align-items: center;
justify-content: center;
background: transparent;
border: none;
padding: 0 4px;
border-radius: 2px;
line-height: 1;
gap: 2px;
}
.pipi-music .collect-btn {
visibility: visible !important;
pointer-events: auto !important;
width: 24px;
height: 24px;
}
.pipi-music .collect-btn .icon-svg {
width: 100%;
height: 100%;
}
.pipi-music .del-btn {
width: 24px;
height: 24px;
display: <?php echo $isAdmin ? 'flex' : 'none'; ?> !important;
}
.pipi-music .del-btn .icon-svg {
width: 100%;
height: 100%;
}
/* ===== 全局提示 ===== */
.pipi-music .global-empty-tip {
max-width: 700px;
margin: 15px auto;
padding: 12px 20px;
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 6px;
text-align: center;
color: #6c757d;
font-size: var(--font-size-base);
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
display: none;
}
/* ===== 上传进度 ===== */
.pipi-music .upload-progress {
width: 300px;
height: 8px;
background-color: #f0f0f0;
border-radius: 4px;
margin: 0 auto 20px;
overflow: hidden;
display: none;
z-index: 9999;
}
.pipi-music .upload-progress-bar {
height: 100%;
width: 0%;
background-color: var(--bj-fuse);
border-radius: 4px;
transition: width 0.3s ease;
}
.pipi-music .upload-btn {
width: 80px;
height: 40px;
background-color: var(--bj-fuse);
color: var(--color-baise);
border: none;
border-radius: 4px;
cursor: pointer;
font-size: var(--font-size-base);
transition: background-color 0.2s;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
}
.pipi-music .search-btn {
width: 80px;
height: 40px;
background-color: var(--bj-fuse);
color: var(--color-baise);
border: none;
border-radius: 4px;
cursor: pointer;
font-size: var(--font-size-base);
transition: background-color 0.2s;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
}
.pipi-music .search-btn:hover,
.pipi-music .upload-btn:hover:not(.uploading) {
background-color: var(--bj-lvse);
}
.pipi-music .upload-btn.uploading {
background-color: var(--color-huise);
cursor: not-allowed;
color: var(--bj-fuse);
}
/* ===== 分页控件 ===== */
.pipi-music .pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
margin: 15px auto 10px;
max-width: 700px;
flex-wrap: wrap;
}
.pipi-music .pagination .page-item {
list-style: none;
}
.pipi-music .pagination .page-link {
display: flex;
align-items: center;
justify-content: center;
min-width: 36px;
height: 36px;
padding: 0 6px;
border: 1px solid var(--bj-cifuse);
background-color: var(--bj-zhuse);
color: var(--color-chunhei);
font-size: var(--font-size-sm);
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
text-decoration: none;
}
.pipi-music .pagination .page-link:hover {
background-color: var(--bj-shuise);
border-color: var(--bj-fuse);
}
.pipi-music .pagination .page-item.active .page-link {
background-color: var(--bj-fuse);
color: var(--color-baise);
border-color: var(--bj-fuse);
}
.pipi-music .pagination .page-item.disabled .page-link {
opacity: 0.5;
pointer-events: none;
background-color: var(--bj-zhuse) !important;
border-color: var(--bj-cifuse) !important;
color: var(--color-chunhei) !important;
}
/* ===== 移动端适配 ===== */
@media screen and (max-width: 768px) {
.pipi-music .search-input {
height: 36px;
font-size: var(--font-size-sm);
}
.pipi-music .search-btn,
.pipi-music .upload-btn {
height: 36px;
font-size: var(--font-size-sm);
width: 70px;
}
.pipi-music .upload-progress {
width: 200px;
}
.pipi-music .control-btn {
width: 55px;
height: 55px;
}
.pipi-music .control-btn.small {
width: 30px;
height: 30px;
}
.pipi-music .mode-btn {
width: 40px;
height: 40px;
}
.pipi-music .icon-svg.lg {
width: 25px;
height: 25px;
}
.pipi-music .icon-svg.md {
width: 18px;
height: 18px;
}
.pipi-music .icon-svg.sm {
width: 16px;
height: 16px;
}
.pipi-music .volume-area {
width: 80px;
gap: 1px;
}
.pipi-music .volume-area .btn {
width: 26px;
height: 26px;
}
.pipi-music .volume-bar {
width: 50px;
}
.pipi-music .lyric-container {
height: 160px;
padding: 10px 15px;
}
.pipi-music .music-item {
width: 100%;
padding: 6px 10px;
gap: 8px;
}
.pipi-music .music-num {
width: 18px;
height: 18px;
font-size: 12px;
}
.pipi-music .text-btn {
height: 18px;
padding: 0 3px;
}
.pipi-music .search-box {
margin-top: 45px;
}
.pipi-music .global-empty-tip {
margin: 10px 15px;
padding: 10px 15px;
font-size: var(--font-size-sm);
}
.pipi-music .player-controls {
gap: 8px;
}
.pipi-music .pagination .page-link {
min-width: 32px;
height: 32px;
font-size: var(--font-size-xs);
}
}
</style>
<?php $this->need('sidebar.php'); ?>
<div class="pipi-music">
<div class="sm-mainContent">
<div class="sm-innermainContent">
<section>
<article>
<div class="music-wrap">
<h1 class="music-title">皮皮音乐盒</h1>
<div class="upload-progress" id="upload-progress-container">
<div class="upload-progress-bar" id="upload-progress-bar"></div>
</div>
<div class="search-box">
<input type="text" class="search-input" id="pipi-search-input" placeholder="输入歌曲名搜索...">
<button class="search-btn" id="pipi-search-btn">
<span class="icon-svg sm icon-search"></span> 搜索
</button>
<input type="file" id="pipi-file-input" multiple accept=".mp3,.lrc,.jpg,.jpeg,.png,.gif,.webp" style="display:none;">
<label for="pipi-file-input" class="upload-btn switch-btn admin-only" id="pipi-file-input-label">
<span class="icon-svg sm icon-upload"></span> 上传
</label>
</div>
<div class="global-empty-tip" id="global-empty-tip">
未找到匹配的歌曲,请更换关键词重试!
</div>
<div class="player-box">
<div class="current-playing">
<div class="current-playing-name" id="pipi-now-playing-name">点击按钮随机播放歌曲</div>
</div>
<div class="player-controls">
<button class="mode-btn active" id="all-mode-btn" title="全部歌曲">
<span class="icon-svg md icon-list"></span>
</button>
<button class="control-btn small" id="pipi-prev-btn" title="上一首">
<span class="icon-svg md icon-prev"></span>
</button>
<button class="control-btn" id="pipi-play-pause-btn" title="播放/暂停">
<span class="icon-svg lg icon-play" id="play-pause-icon"></span>
</button>
<button class="control-btn small" id="pipi-next-btn" title="下一首">
<span class="icon-svg md icon-next"></span>
</button>
<button class="mode-btn" id="collect-mode-btn" title="收藏歌曲">
<span class="icon-svg md icon-collect"></span>
</button>
</div>
<div class="progress-volume-group">
<div class="progress-volume-wrap">
<div class="progress-area">
<div class="progress-bar" id="pipi-progress-bar">
<div class="progress-fill" id="pipi-progress-fill"></div>
</div>
</div>
<div class="volume-area">
<button class="btn" id="pipi-volume-btn" title="静音/取消静音">
<span class="icon-svg sm icon-volume" id="volume-icon"></span>
</button>
<div class="volume-bar" id="pipi-volume-bar">
<div class="volume-fill" id="pipi-volume-fill"></div>
</div>
</div>
</div>
<div class="time-display">
<span id="pipi-current-time">00:00</span> / <span id="pipi-total-time">00:00</span>
</div>
<div class="lyric-container" id="pipi-lyric-container">
<img src="" alt="" class="music-image" id="pipi-music-image" style="display: none;">
<div class="empty-tip">欢迎来访皮皮音乐盒 www.pipishe.com</div>
</div>
</div>
</div>
<!-- 歌曲列表容器(JS动态渲染) -->
<div class="music-list" id="pipi-music-list-anchor"></div>
<!-- 分页容器 -->
<div class="pagination" id="pipi-pagination"></div>
<br>
<div class="sm-Content-bottom">
<div>
评论: <?php $this->commentsNum('0', '1', '%d'); ?> |
查看: <?php
$cid = $this->cid;
$key = 'postViews_' . $cid;
$countKey = 'view_times_' . $cid;
$lastTimeKey = 'view_last_' . $cid;
$now = time();
$userCount = (int)Typecho_Cookie::get($countKey);
$lastTime = (int)Typecho_Cookie::get($lastTimeKey);
// 1天最多60次 + 间隔4秒(人性化真实阅读量)
if ($userCount < 60 && $now - $lastTime >= 4) {
$db = Typecho_Db::get();
$row = $db->fetchRow($db->select('value')->from('table.options')->where('name = ?', $key)->limit(1));
$num = $row ? intval($row['value']) : 0;
$num++;
if ($row) {
$db->query($db->update('table.options')->where('name = ?', $key)->rows(['value' => $num]));
} else {
$db->query($db->insert('table.options')->rows(['name' => $key, 'value' => $num, 'user' => 0]));
}
Typecho_Cookie::set($countKey, $userCount + 1, time() + 86400);
Typecho_Cookie::set($lastTimeKey, $now, time() + 86400);
}
echo xm::get_post_view($this);
?>
</div>
</div>
<div class="comment-area">
<?php $this->need('comments.php'); ?>
</div>
</div>
</article>
</section>
</div>
</div>
</div>
<script>
// ========== JavaScript 部分 ==========
let audio, originalMusicList, musicList;
let currentIndex = -1, isPlaying = false, currentLyrics = [], volume = 0.8;
let lyricListEl = null, lyricContainerHeight = 0, lineHeight = 0;
let collectMode = false;
let collectList = JSON.parse(localStorage.getItem('pipiMusicCollect') || '[]') || [];
let currentPlayInfo = { name: '', url: '', file: '', image: '' };
// 分页相关变量
let currentPage = 1;
const pageSize = 20;
let totalPages = 1;
const el = {};
document.addEventListener('DOMContentLoaded', () => {
audio = new Audio();
originalMusicList = <?php echo json_encode($fullMusicList) ?: '[]'; ?>;
musicList = originalMusicList ? [...originalMusicList] : [];
Object.assign(el, {
playPauseBtn: document.getElementById('pipi-play-pause-btn'),
playPauseIcon: document.getElementById('play-pause-icon'),
prevBtn: document.getElementById('pipi-prev-btn'),
nextBtn: document.getElementById('pipi-next-btn'),
progressBar: document.getElementById('pipi-progress-bar'),
progressFill: document.getElementById('pipi-progress-fill'),
currentTimeEl: document.getElementById('pipi-current-time'),
totalTimeEl: document.getElementById('pipi-total-time'),
volumeBtn: document.getElementById('pipi-volume-btn'),
volumeIcon: document.getElementById('volume-icon'),
volumeBar: document.getElementById('pipi-volume-bar'),
volumeFill: document.getElementById('pipi-volume-fill'),
lyricContainer: document.getElementById('pipi-lyric-container'),
musicImage: document.getElementById('pipi-music-image'),
musicListEl: document.getElementById('pipi-music-list-anchor'),
paginationEl: document.getElementById('pipi-pagination'),
nowPlayingName: document.getElementById('pipi-now-playing-name'),
searchInput: document.getElementById('pipi-search-input'),
searchBtn: document.getElementById('pipi-search-btn'),
allModeBtn: document.getElementById('all-mode-btn'),
collectModeBtn: document.getElementById('collect-mode-btn'),
globalEmptyTip: document.getElementById('global-empty-tip'),
uploadBtn: document.getElementById('pipi-file-input-label'),
fileInput: document.getElementById('pipi-file-input'),
uploadProgressBar: document.getElementById('upload-progress-bar'),
progressContainer: document.getElementById('upload-progress-container')
});
lyricContainerHeight = el.lyricContainer.offsetHeight;
audio.volume = volume;
el.volumeFill.style.width = `${volume * 100}%`;
audio.addEventListener('volumechange', () => {
if (el.volumeIcon) {
el.volumeIcon.className = audio.muted
? 'icon-svg sm icon-volume-mute'
: 'icon-svg sm icon-volume';
}
});
audio.addEventListener('loadedmetadata', () => {
el.totalTimeEl.textContent = formatTime(audio.duration);
});
audio.addEventListener('canplaythrough', () => {
el.totalTimeEl.textContent = formatTime(audio.duration);
});
initCollectStatus();
renderMusicList();
updateControlButtonStatus();
bindEvents();
});
function formatTime(seconds) {
if (isNaN(seconds) || seconds === Infinity) return '00:00';
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
function htmlEscape(str) {
if (!str) return '';
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
function scrollToMusicList() {
el.musicListEl.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
function renderMusicList(list = musicList) {
if (!el.musicListEl) return;
totalPages = Math.ceil(list.length / pageSize) || 1;
if (currentPage > totalPages) currentPage = totalPages;
if (currentPage < 1) currentPage = 1;
const start = (currentPage - 1) * pageSize;
const end = start + pageSize;
const pageData = list.slice(start, end);
if (pageData.length === 0) {
el.musicListEl.innerHTML = '<div class="empty-tip">暂无歌曲</div>';
} else {
let html = '';
pageData.forEach((item, idx) => {
const globalIndex = start + idx;
const isCollected = collectList.includes(item.file);
const isPlaying = currentPlayInfo.file === item.file;
const delBtnHtml = <?php echo $isAdmin ? '`<button class="text-btn del-btn" title="删除歌曲"><span class="icon-svg sm icon-delete"></span></button>`' : '""'; ?>;
// 构建图标标识
let iconsHtml = '';
if (item.lyricUrl) {
iconsHtml += '<span class="icon-svg sm icon-lyric" title="有歌词"></span>';
}
if (item.imageUrl) {
iconsHtml += '<span class="icon-svg sm icon-image" title="有封面"></span>';
}
if (iconsHtml) {
iconsHtml = `<span class="music-icons">${iconsHtml}</span>`;
}
html += `
<div class="music-item ${isPlaying ? 'active' : ''}"
data-id="${item.id}"
data-file="${htmlEscape(item.file)}"
data-url="${htmlEscape(item.url)}"
data-lyric="${htmlEscape(item.lyricUrl)}"
data-image="${htmlEscape(item.imageUrl || '')}"
data-name="${htmlEscape(item.name)}">
<div class="music-num">${globalIndex + 1}</div>
<div class="music-name">
${htmlEscape(item.name)}${iconsHtml}
<span class="music-wave-animate">
<span></span>
<span></span>
<span></span>
</span>
</div>
<span class="text-btn collect-btn ${isCollected ? 'collected' : ''}"
data-file="${htmlEscape(item.file)}"
title="${isCollected ? '取消收藏' : '收藏'}">
<span class="icon-svg sm ${isCollected ? 'icon-collect-selected' : 'icon-collect-normal'}"></span>
</span>
${delBtnHtml}
</div>`;
});
el.musicListEl.innerHTML = html;
}
bindMusicItemEvents();
renderPagination(list.length);
}
function renderPagination(totalItems) {
if (!el.paginationEl) return;
if (totalItems <= pageSize) {
el.paginationEl.innerHTML = '';
return;
}
let html = '<ul style="display:flex; gap:5px; list-style:none; padding:0; margin:0;">';
html += `<li class="page-item ${currentPage === 1 ? 'disabled' : ''}">
<a class="page-link" data-page="prev" href="javascript:void(0)">«</a>
</li>`;
const maxPagesToShow = 5;
let startPage = Math.max(1, currentPage - 2);
let endPage = Math.min(totalPages, currentPage + 2);
if (currentPage <= 3) {
endPage = Math.min(totalPages, maxPagesToShow);
}
if (currentPage > totalPages - 3) {
startPage = Math.max(1, totalPages - maxPagesToShow + 1);
}
for (let i = startPage; i <= endPage; i++) {
html += `<li class="page-item ${i === currentPage ? 'active' : ''}">
<a class="page-link" data-page="${i}" href="javascript:void(0)">${i}</a>
</li>`;
}
html += `<li class="page-item ${currentPage === totalPages ? 'disabled' : ''}">
<a class="page-link" data-page="next" href="javascript:void(0)">»</a>
</li></ul>`;
el.paginationEl.innerHTML = html;
el.paginationEl.querySelectorAll('.page-link').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const page = link.dataset.page;
if (page === 'prev') {
if (currentPage > 1) currentPage--;
} else if (page === 'next') {
if (currentPage < totalPages) currentPage++;
} else {
currentPage = parseInt(page, 10);
}
renderMusicList(musicList);
scrollToMusicList();
});
});
}
function parseLrc(lrcText) {
const lyrics = [];
const reg = /\[(\d{2}):(\d{2})(?:\.(\d{1,3}))?\](.*)/g;
let match;
while ((match = reg.exec(lrcText)) !== null) {
const min = parseInt(match[1]), sec = parseInt(match[2]), ms = match[3] ? parseInt(match[3]) : 0;
const time = min * 60 + sec + (ms / 1000);
const text = match[4].trim();
text && lyrics.push({time, text});
}
return lyrics.sort((a, b) => a.time - b.time);
}
function renderLyrics(lyrics, imageUrl = '') {
currentLyrics = lyrics;
el.lyricContainer.innerHTML = '';
if (el.musicImage) {
if (imageUrl) {
el.musicImage.src = imageUrl;
el.musicImage.alt = currentPlayInfo.name || '歌曲封面';
el.musicImage.style.display = 'block';
} else {
el.musicImage.src = '';
el.musicImage.style.display = 'none';
}
el.lyricContainer.appendChild(el.musicImage);
}
if (!lyrics.length) {
const tip = document.createElement('div');
tip.className = 'empty-tip';
tip.textContent = '这首歌曲可能是纯音乐,或是忘记上传歌词了,你可以在下方留言将歌曲名称告知博主。';
el.lyricContainer.appendChild(tip);
return;
}
lyricListEl = document.createElement('div');
lyricListEl.className = 'lyric-list';
lyrics.forEach(lyric => {
const line = document.createElement('div');
line.className = 'lyric-line';
line.textContent = lyric.text;
line.dataset.time = lyric.time;
lyricListEl.appendChild(line);
});
el.lyricContainer.appendChild(lyricListEl);
lyricContainerHeight = el.lyricContainer.offsetHeight;
lineHeight = 0;
}
function syncLyrics() {
if (!currentLyrics.length || !lyricListEl) return;
if (lineHeight === 0) {
const firstLine = lyricListEl.querySelector('.lyric-line');
lineHeight = firstLine ? firstLine.offsetHeight : 28;
}
const currentTime = audio.currentTime;
const lines = lyricListEl.querySelectorAll('.lyric-line');
let activeIdx = -1;
for (let i = 0; i < currentLyrics.length; i++) {
if (currentTime >= currentLyrics[i].time) activeIdx = i;
else break;
}
lines.forEach((line, idx) => {
line.classList.remove('active');
idx === activeIdx && line.classList.add('active');
});
if (activeIdx !== -1) {
const lyricListStyle = window.getComputedStyle(lyricListEl);
const listPaddingTop = parseFloat(lyricListStyle.paddingTop) || 0;
const listPaddingBottom = parseFloat(lyricListStyle.paddingBottom) || 0;
const listTotalPadding = listPaddingTop + listPaddingBottom;
const centerOffset = (lyricContainerHeight / 2) - (lineHeight / 2) - (listTotalPadding / 2);
const topOffset = activeIdx * lineHeight;
const maxScrollTop = Math.max(0, lyricListEl.offsetHeight - lyricContainerHeight);
const finalTop = Math.min(centerOffset - topOffset, maxScrollTop);
lyricListEl.style.top = `${finalTop}px`;
}
}
function syncPlayPauseIcons() {
if (!el.playPauseIcon) return;
el.playPauseIcon.className = isPlaying ? 'icon-svg lg icon-pause' : 'icon-svg lg icon-play';
}
function updateControlButtonStatus() {
const hasMusicList = musicList && musicList.length > 0;
const hasCurrentPlay = !!currentPlayInfo.url;
if (el.prevBtn) {
el.prevBtn.disabled = !hasMusicList;
el.prevBtn.classList.toggle('disabled', !hasMusicList);
}
if (el.nextBtn) {
el.nextBtn.disabled = !hasMusicList;
el.nextBtn.classList.toggle('disabled', !hasMusicList);
}
if (el.playPauseBtn) {
el.playPauseBtn.disabled = !hasCurrentPlay && !hasMusicList;
el.playPauseBtn.classList.toggle('disabled', !hasCurrentPlay && !hasMusicList);
}
}
function updateModeButtons() {
if (!el.allModeBtn || !el.collectModeBtn) return;
el.allModeBtn.classList.toggle('active', !collectMode);
el.collectModeBtn.classList.toggle('active', collectMode);
}
function getCollectMusicList() {
return originalMusicList ? originalMusicList.filter(item => collectList.includes(item.file)) : [];
}
function initCollectStatus() {
document.querySelectorAll('.collect-btn').forEach(tag => {
const file = tag.dataset.file;
const iconEl = tag.querySelector('.icon-svg');
if (collectList.includes(file)) {
tag.classList.add('collected');
iconEl.className = 'icon-svg sm icon-collect-selected';
tag.title = '取消收藏';
} else {
tag.classList.remove('collected');
iconEl.className = 'icon-svg sm icon-collect-normal';
tag.title = '收藏歌曲';
}
});
}
function toggleCollect(file) {
const index = collectList.indexOf(file);
index > -1 ? collectList.splice(index, 1) : collectList.push(file);
localStorage.setItem('pipiMusicCollect', JSON.stringify(collectList));
initCollectStatus();
// 重新渲染当前列表(保持分页)
if (collectMode) {
musicList = getCollectMusicList();
} else {
musicList = [...originalMusicList];
}
renderMusicList(musicList);
}
function switchMode(isCollect) {
if (!el.globalEmptyTip) return;
el.globalEmptyTip.style.display = 'none';
collectMode = isCollect;
updateModeButtons();
const currentPlayFile = currentPlayInfo.file;
const currentPlayLyrics = currentLyrics;
const currentPlayImage = currentPlayInfo.image;
musicList = isCollect ? getCollectMusicList() : [...(originalMusicList || [])];
currentPage = 1; // 切换模式重置到第一页
renderMusicList(musicList);
if (currentPlayFile) {
const currentItem = musicList.find(item => item.file === currentPlayFile);
if (currentItem) {
currentIndex = musicList.findIndex(item => item.file === currentPlayFile);
el.nowPlayingName.textContent = `正在播放:${currentItem.name}`;
currentPlayInfo = {
name: currentItem.name,
url: currentItem.url,
file: currentItem.file,
image: currentItem.imageUrl
};
if (currentPlayLyrics.length > 0) {
renderLyrics(currentPlayLyrics, currentItem.imageUrl);
} else if (currentItem.lyricUrl) {
fetch(currentItem.lyricUrl)
.then(res => res.text())
.then(text => renderLyrics(parseLrc(text), currentItem.imageUrl))
.catch(() => renderLyrics([], currentItem.imageUrl));
} else {
renderLyrics([], currentItem.imageUrl);
}
} else {
el.nowPlayingName.textContent = `正在播放:${currentPlayInfo.name}(当前列表无此歌曲)`;
if (currentPlayLyrics.length > 0) renderLyrics(currentPlayLyrics, currentPlayImage);
}
}
updateControlButtonStatus();
syncPlayPauseIcons();
}
function searchMusic(keyword) {
if (!el.globalEmptyTip || !el.musicListEl) return;
el.globalEmptyTip.style.display = 'none';
if (!keyword.trim()) {
switchMode(collectMode);
scrollToMusicList();
return;
}
const lowerKeyword = keyword.trim().toLowerCase();
const sourceList = collectMode ? getCollectMusicList() : (originalMusicList || []);
musicList = sourceList.filter(item => item.name.toLowerCase().includes(lowerKeyword));
currentPage = 1; // 搜索重置到第一页
if (musicList.length === 0) {
el.globalEmptyTip.style.display = 'block';
el.musicListEl.innerHTML = '<div class="empty-tip">未找到匹配的歌曲</div>';
el.paginationEl.innerHTML = ''; // 清空分页
} else {
renderMusicList(musicList);
}
scrollToMusicList();
}
function bindMusicItemEvents() {
document.querySelectorAll('.music-item').forEach(item => {
item.addEventListener('click', (e) => {
if (e.target.closest('.text-btn')) return;
const sourceList = collectMode ? getCollectMusicList() : originalMusicList;
const file = item.dataset.file;
currentIndex = sourceList.findIndex(i => i.file === file);
const url = item.dataset.url;
const name = item.dataset.name;
const lyricUrl = item.dataset.lyric;
const imageUrl = item.dataset.image;
currentPlayInfo = { name, url, file, image: imageUrl };
audio.src = url;
el.nowPlayingName.textContent = `正在播放:${name}`;
document.querySelectorAll('.music-item').forEach(i => i.classList.remove('active'));
item.classList.add('active');
if (lyricUrl) {
fetch(lyricUrl)
.then(res => res.text())
.then(text => renderLyrics(parseLrc(text), imageUrl))
.catch(() => renderLyrics([], imageUrl));
} else {
renderLyrics([], imageUrl);
}
audio.play().then(() => {
isPlaying = true;
syncPlayPauseIcons();
updateControlButtonStatus();
}).catch(err => {
console.error('播放失败:', err);
alert('播放失败,请检查音频文件是否有效!');
});
});
const collectBtn = item.querySelector('.collect-btn');
collectBtn?.addEventListener('click', (e) => {
e.stopPropagation();
toggleCollect(collectBtn.dataset.file);
});
const delBtn = item.querySelector('.del-btn');
delBtn?.addEventListener('click', (e) => {
e.stopPropagation();
if (!confirm('确定要删除这首歌曲吗?删除后不可恢复!')) return;
const file = item.dataset.file;
fetch(window.location.href, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `action=delete&file=${encodeURIComponent(file)}`
})
.then(res => res.json())
.then(data => {
if (data.success) {
alert(data.msg);
window.location.reload();
} else {
alert(data.msg);
}
}).catch(err => {
alert('删除请求失败,请重试');
console.error(err);
});
});
});
}
function handleEmptyCollectModeSwitch() {
switchMode(false);
alert('收藏列表为空,已自动切换到全部歌曲列表');
if (musicList && musicList.length > 0) {
currentIndex = 0;
const item = musicList[currentIndex];
if (item) {
audio.src = item.url;
el.nowPlayingName.textContent = `正在播放:${item.name}`;
currentPlayInfo = {
name: item.name,
url: item.url,
file: item.file,
image: item.imageUrl
};
if (item.lyricUrl) {
fetch(item.lyricUrl)
.then(res => res.text())
.then(text => renderLyrics(parseLrc(text), item.imageUrl))
.catch(() => renderLyrics([], item.imageUrl));
} else {
renderLyrics([], item.imageUrl);
}
document.querySelectorAll('.music-item').forEach((i, idx) => {
idx === currentIndex ? i.classList.add('active') : i.classList.remove('active');
});
audio.play().then(() => {
isPlaying = true;
syncPlayPauseIcons();
}).catch(err => {
console.error('播放失败:', err);
alert('播放失败,请检查音频文件是否有效!');
});
}
}
}
function bindEvents() {
el.playPauseBtn?.addEventListener('click', () => {
const hasMusicList = musicList && musicList.length > 0;
const hasCurrentPlay = !!currentPlayInfo.url;
if (hasCurrentPlay) {
if (isPlaying) {
audio.pause();
} else {
audio.play().catch(err => {
console.error('播放失败:', err);
alert('播放失败,请检查音频文件是否有效!');
});
}
isPlaying = !isPlaying;
syncPlayPauseIcons();
return;
}
if (hasMusicList) {
currentIndex = Math.floor(Math.random() * musicList.length);
const item = musicList[currentIndex];
if (!item) return;
audio.src = item.url;
el.nowPlayingName.textContent = `正在播放:${item.name}`;
currentPlayInfo = {
name: item.name,
url: item.url,
file: item.file,
image: item.imageUrl
};
if (item.lyricUrl) {
fetch(item.lyricUrl)
.then(res => res.text())
.then(text => renderLyrics(parseLrc(text), item.imageUrl))
.catch(() => renderLyrics([], item.imageUrl));
} else {
renderLyrics([], item.imageUrl);
}
audio.play().then(() => {
isPlaying = true;
syncPlayPauseIcons();
updateControlButtonStatus();
}).catch(err => {
console.error('播放失败:', err);
alert('播放失败,请检查音频文件是否有效!');
});
} else {
alert('暂无歌曲可播放');
}
});
el.prevBtn?.addEventListener('click', () => {
if (!musicList || musicList.length === 0) {
if (collectMode) {
handleEmptyCollectModeSwitch();
}
return;
}
currentIndex = (currentIndex - 1 + musicList.length) % musicList.length;
const item = musicList[currentIndex];
if (!item) return;
audio.src = item.url;
el.nowPlayingName.textContent = `正在播放:${item.name}`;
currentPlayInfo = {
name: item.name,
url: item.url,
file: item.file,
image: item.imageUrl
};
if (item.lyricUrl) {
fetch(item.lyricUrl)
.then(res => res.text())
.then(text => renderLyrics(parseLrc(text), item.imageUrl))
.catch(() => renderLyrics([], item.imageUrl));
} else {
renderLyrics([], item.imageUrl);
}
document.querySelectorAll('.music-item').forEach(i => i.classList.remove('active'));
const activeItem = document.querySelector(`.music-item[data-file="${item.file}"]`);
if (activeItem) activeItem.classList.add('active');
audio.play().then(() => {
isPlaying = true;
syncPlayPauseIcons();
}).catch(err => {
console.error('播放失败:', err);
alert('播放失败,请检查音频文件是否有效!');
});
});
el.nextBtn?.addEventListener('click', () => {
if (!musicList || musicList.length === 0) {
if (collectMode) {
handleEmptyCollectModeSwitch();
}
return;
}
currentIndex = (currentIndex + 1) % musicList.length;
const item = musicList[currentIndex];
if (!item) return;
audio.src = item.url;
el.nowPlayingName.textContent = `正在播放:${item.name}`;
currentPlayInfo = {
name: item.name,
url: item.url,
file: item.file,
image: item.imageUrl
};
if (item.lyricUrl) {
fetch(item.lyricUrl)
.then(res => res.text())
.then(text => renderLyrics(parseLrc(text), item.imageUrl))
.catch(() => renderLyrics([], item.imageUrl));
} else {
renderLyrics([], item.imageUrl);
}
document.querySelectorAll('.music-item').forEach(i => i.classList.remove('active'));
const activeItem = document.querySelector(`.music-item[data-file="${item.file}"]`);
if (activeItem) activeItem.classList.add('active');
audio.play().then(() => {
isPlaying = true;
syncPlayPauseIcons();
}).catch(err => {
console.error('播放失败:', err);
alert('播放失败,请检查音频文件是否有效!');
});
});
el.progressBar?.addEventListener('click', function(e) {
const rect = this.getBoundingClientRect();
const clickX = e.clientX - rect.left;
const percent = clickX / rect.width;
if (!isNaN(audio.duration)) {
audio.currentTime = audio.duration * percent;
el.progressFill.style.width = `${percent * 100}%`;
el.currentTimeEl.textContent = formatTime(audio.currentTime);
}
});
el.volumeBar?.addEventListener('click', (e) => {
const rect = el.volumeBar.getBoundingClientRect();
const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
volume = ratio;
audio.volume = volume;
el.volumeFill.style.width = `${ratio * 100}%`;
if (audio.muted) {
audio.muted = false;
el.volumeFill.style.opacity = '1';
if (el.volumeIcon) {
el.volumeIcon.className = 'icon-svg sm icon-volume';
}
}
});
el.volumeBtn?.addEventListener('click', () => {
audio.muted = !audio.muted;
el.volumeFill.style.opacity = audio.muted ? '0.3' : '1';
if (el.volumeIcon) {
el.volumeIcon.className = audio.muted
? 'icon-svg sm icon-volume-mute'
: 'icon-svg sm icon-volume';
}
});
audio.addEventListener('timeupdate', () => {
const ratio = audio.currentTime / audio.duration;
el.progressFill.style.width = `${isNaN(ratio) ? 0 : ratio * 100}%`;
el.currentTimeEl.textContent = formatTime(audio.currentTime);
syncLyrics();
});
audio.addEventListener('ended', () => {
if (!musicList || musicList.length === 0) {
if (collectMode) {
switchMode(false);
if (musicList && musicList.length > 0) {
currentIndex = 0;
const item = musicList[currentIndex];
if (item) {
audio.src = item.url;
el.nowPlayingName.textContent = `正在播放:${item.name}`;
currentPlayInfo = {
name: item.name,
url: item.url,
file: item.file,
image: item.imageUrl
};
if (item.lyricUrl) {
fetch(item.lyricUrl)
.then(res => res.text())
.then(text => renderLyrics(parseLrc(text), item.imageUrl))
.catch(() => renderLyrics([], item.imageUrl));
} else {
renderLyrics([], item.imageUrl);
}
document.querySelectorAll('.music-item').forEach(i => i.classList.remove('active'));
const activeItem = document.querySelector(`.music-item[data-file="${item.file}"]`);
if (activeItem) activeItem.classList.add('active');
audio.play().then(() => {
isPlaying = true;
syncPlayPauseIcons();
}).catch(err => {
console.error('播放失败:', err);
alert('播放失败,请检查音频文件是否有效!');
});
}
}
}
return;
}
currentIndex = (currentIndex + 1) % musicList.length;
const item = musicList[currentIndex];
if (!item) return;
audio.src = item.url;
el.nowPlayingName.textContent = `正在播放:${item.name}`;
currentPlayInfo = {
name: item.name,
url: item.url,
file: item.file,
image: item.imageUrl
};
if (item.lyricUrl) {
fetch(item.lyricUrl)
.then(res => res.text())
.then(text => renderLyrics(parseLrc(text), item.imageUrl))
.catch(() => renderLyrics([], item.imageUrl));
} else {
renderLyrics([], item.imageUrl);
}
document.querySelectorAll('.music-item').forEach(i => i.classList.remove('active'));
const activeItem = document.querySelector(`.music-item[data-file="${item.file}"]`);
if (activeItem) activeItem.classList.add('active');
audio.play().then(() => {
isPlaying = true;
syncPlayPauseIcons();
}).catch(err => {
console.error('播放失败:', err);
alert('播放失败,请检查音频文件是否有效!');
});
});
audio.addEventListener('play', () => {
isPlaying = true;
syncPlayPauseIcons();
});
audio.addEventListener('pause', () => {
isPlaying = false;
syncPlayPauseIcons();
});
audio.addEventListener('error', () => {
console.error('音频加载失败:', audio.error);
alert(`播放失败:${audio.error?.message || '音频文件无法加载'}`);
isPlaying = false;
syncPlayPauseIcons();
currentPlayInfo = { name: '', url: '', file: '', image: '' };
el.nowPlayingName.textContent = '播放失败:音频文件异常';
updateControlButtonStatus();
});
el.fileInput?.addEventListener('change', () => {
const files = el.fileInput.files;
if (!files || files.length === 0) return;
const formData = new FormData();
for (let i = 0; i < files.length; i++) {
formData.append('upload_files[]', files[i]);
}
if (el.progressContainer) {
el.progressContainer.style.display = 'block';
}
if (el.uploadBtn) {
el.uploadBtn.classList.add('uploading');
el.uploadBtn.innerHTML = '<span class="icon-svg sm icon-upload"></span> 上传中';
el.uploadBtn.style.pointerEvents = 'none';
}
const xhr = new XMLHttpRequest();
xhr.open('POST', window.location.href, true);
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable && el.uploadProgressBar) {
const percent = Math.round((e.loaded / e.total) * 100);
el.uploadProgressBar.style.width = `${percent}%`;
}
});
xhr.addEventListener('load', () => {
let data = { success: false, msg: '上传失败:响应格式异常' };
try {
const responseText = xhr.responseText ? xhr.responseText.trim() : '';
if (responseText) {
data = JSON.parse(responseText);
} else if (xhr.status >= 200 && xhr.status < 300) {
data = { success: true, msg: '✅ 上传成功!页面将自动刷新' };
}
} catch (err) {
console.error('解析上传响应失败:', err);
}
if (el.progressContainer) el.progressContainer.style.display = 'none';
if (el.uploadBtn) {
el.uploadBtn.classList.remove('uploading');
el.uploadBtn.innerHTML = '<span class="icon-svg sm icon-upload"></span> 上传';
el.uploadBtn.style.pointerEvents = 'auto';
}
// 统一使用漂亮提示条
function showToast(message, isSuccess = true) {
const toast = document.createElement('div');
toast.style.cssText = `
position: fixed; top: 20px; left: 50%; transform: translateX(-50%);
background: ${isSuccess ? '#4CAF50' : '#F56C6C'};
color: #fff; padding: 12px 24px; border-radius: 6px;
z-index: 9999; font-size: 14px; box-shadow: 0 2px 10px rgba(0,0,0,0.2);
white-space: pre-line;
`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.remove();
}, 3000);
}
// 成功
if (data.success) {
showToast(data.msg, true);
setTimeout(() => {
window.location.reload();
}, 2000);
}
// 失败(也用漂亮提示条)
else {
showToast(data.msg, false);
}
});
xhr.addEventListener('error', () => {
if (el.progressContainer) el.progressContainer.style.display = 'none';
if (el.uploadBtn) {
el.uploadBtn.classList.remove('uploading');
el.uploadBtn.innerHTML = '<span class="icon-svg sm icon-upload"></span> 上传';
el.uploadBtn.style.pointerEvents = 'auto';
}
alert('❌ 上传失败:网络错误,请重试!');
});
xhr.addEventListener('abort', () => {
if (el.progressContainer) el.progressContainer.style.display = 'none';
if (el.uploadBtn) {
el.uploadBtn.classList.remove('uploading');
el.uploadBtn.innerHTML = '<span class="icon-svg sm icon-upload"></span> 上传';
el.uploadBtn.style.pointerEvents = 'auto';
}
alert('⚠️ 上传已取消!');
});
xhr.send(formData);
});
el.searchInput?.addEventListener('keyup', (e) => {
e.key === 'Enter' && searchMusic(el.searchInput.value);
});
el.searchBtn?.addEventListener('click', () => {
searchMusic(el.searchInput.value);
});
el.allModeBtn?.addEventListener('click', () => {
switchMode(false);
});
el.collectModeBtn?.addEventListener('click', () => {
switchMode(true);
});
}
window.addEventListener('beforeunload', () => {
audio.pause();
});
</script>
<?php $this->need('footer.php'); ?>文章页调用代码:
<?php
/**
* 文章调用音乐盒音乐V0.16
*/
function parseMusicShortcode($content) {
if (empty($content)) return '';
$pattern = '/\[music\s+([^\]]*)\]/i';
$content = preg_replace_callback($pattern, function($matches) {
static $musicIndex = 0;
$musicIndex++;
$attrs = $matches[1] ?? '';
$musicName = '';
$musicMp3 = '';
$customCover = '';
$customLrc = '';
$listName = '';
if (preg_match('/(?:名称|name)="([^"]*)"/i', $attrs, $m)) $musicName = trim($m[1]);
if (preg_match('/mp3="([^"]*)"/i', $attrs, $m)) $musicMp3 = trim($m[1]);
if (preg_match('/pic="([^"]*)"/i', $attrs, $m)) $customCover = trim($m[1]);
if (preg_match('/lrc="([^"]*)"/i', $attrs, $m)) $customLrc = trim($m[1]);
if (preg_match('/列表="([^"]*)"/i', $attrs, $m)) $listName = trim($m[1]);
$isPlaylist = !empty($listName);
$playlist = [];
$music = [];
if ($isPlaylist) {
$names = explode('|', $listName);
foreach ($names as $i => $name) {
$name = trim($name);
if (empty($name)) continue;
$song = getSingleMusicForShortcode($name);
if (!empty($song['url'])) {
$playlist[] = [
'name' => $name,
'url' => $song['url'],
'cover' => $song['cover'] ?? '',
'lyrics' => parseLrcLyrics($song['lyrics'])
];
}
}
} else {
if (!empty($musicMp3)) {
$music = getMusicByMp3($musicMp3);
} else {
$music = getSingleMusicForShortcode($musicName);
}
}
$realMusicName = '';
if (!$isPlaylist) {
$realMusicName = !empty($musicName) ? $musicName : ($music['name'] ?? '未知歌曲');
}
$lyrics_str = '';
$music_url = '';
$music_cover = '';
$defaultCover = '/usr/themes/dage/bai/img/tx.png';
if (!$isPlaylist) {
$lyrics_str = !empty($customLrc) ? @file_get_contents($customLrc) : ($music['lyrics'] ?? '');
$music_url = !empty($musicMp3) ? $musicMp3 : ($music['url'] ?? '');
$music_cover = !empty($customCover) ? $customCover : (!empty($music['cover']) ? $music['cover'] : $defaultCover);
} else {
$music_url = $playlist[0]['url'] ?? '';
$music_cover = !empty($playlist[0]['cover']) ? $playlist[0]['cover'] : $defaultCover;
}
$lyrics = !$isPlaylist ? parseLrcLyrics($lyrics_str) : $playlist[0]['lyrics'] ?? [];
$initLyricText = htmlspecialchars($isPlaylist ? ($playlist[0]['name'] ?? '未知歌曲') : $realMusicName);
if (empty($music_url) && !$isPlaylist) {
$tip = !empty($musicMp3) ? "歌曲路径无效:{$musicMp3}" : "未找到歌曲:{$musicName}";
return "<div style='color:red;padding:10px;margin:10px 0;background:#fdd;border-radius:5px;'>$tip</div>";
}
if ($isPlaylist && empty($playlist)) {
return "<div style='color:red;padding:10px;margin:10px 0;background:#fdd;border-radius:5px;'>歌曲列表格式错误</div>";
}
$playIconId = "play-icon-$musicIndex";
$volumeIconId = "volume-icon-$musicIndex";
$playerId = "music-player-$musicIndex";
$lyricsId = "music-lyrics-$musicIndex";
$basketballId = "basketball-icon-$musicIndex";
$listId = "music-list-$musicIndex";
$svgPlay = '<img src="/usr/themes/dage/bai/img/bofangqi/调用播放.svg" style="width:20px;height:20px;">';
$svgPause = '<img src="/usr/themes/dage/bai/img/bofangqi/调用暂停.svg" style="width:20px;height:20px;">';
$svgVolume = '<img src="/usr/themes/dage/bai/img/bofangqi/音量.svg" style="width:16px;height:16px;">';
$svgVolumeMute= '<img src="/usr/themes/dage/bai/img/bofangqi/静音.svg" style="width:16px;height:16px;">';
$lyricsJson = json_encode($lyrics, JSON_UNESCAPED_UNICODE);
$songNameJson = json_encode($initLyricText, JSON_UNESCAPED_UNICODE);
$playlistJson = $isPlaylist ? json_encode($playlist, JSON_UNESCAPED_UNICODE) : '[]';
$listHtml = '';
if ($isPlaylist) {
$listHtml = <<<LIST
<div id="$listId" class="music-play-list" style="margin-top:-1px; padding:5px 8px; background:var(--bj-zhuse);border-radius:0 0 8px 8px; border:1px solid var(--bj-fuse); border-top:none;max-height:230px; overflow-y:auto; box-sizing:border-box;">
LIST;
foreach ($playlist as $key => $song) {
$num = $key + 1;
$active = $key === 0 ? 'background:rgba(166,215,69,0.1);color:#a6d745;' : '';
$listHtml .= <<<SONG
<div class="music-list-item" data-index="$key" style="padding:8px 10px; margin:4px 0; border-radius:4px;cursor:pointer; font-size:13px; color:var(--color-huise);transition:all 0.2s; display:flex; align-items:center; justify-content:space-between;gap:8px; $active">
<div style="display:flex; align-items:center; gap:8px; flex:1; min-width:0;">
<span style="flex-shrink:0; opacity:0.6;">$num.</span>
<span style="white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">{$song['name']}</span>
</div>
<span style="flex-shrink:0; font-size:12px; opacity:0.7;" class="list-duration">--:--</span>
</div>
SONG;
}
$listHtml .= '</div>';
}
$radius = $isPlaylist ? '8px 8px 0 0' : '8px 8px 8px 8px';
$playerHtml = <<<HTML
<div style="margin:15px 0 8px; width:100%; box-sizing:border-box;">
<div class="custom-music-player" data-music-index="$musicIndex" style="padding:0; background:var(--bj-zhuse); border-radius:$radius;box-shadow:0 2px 8px rgba(166,215,69,0.08), inset 0 0 0 1px var(--bj-fuse);position:relative; overflow:hidden; height:60px; box-sizing:border-box;display:flex; align-items:center; width:100%; max-width:100%;">
<audio id="$playerId" class="single-music-player" src="$music_url" preload="auto" style="display:none;"></audio>
<div class="music-play-btn" data-index="$musicIndex" style="width:60px; height:60px; border-radius:8px 0 0 0;background:url($music_cover) center center / cover no-repeat;display:flex; align-items:center; justify-content:center;cursor:pointer; flex-shrink:0; position:relative; border:none; margin:0;-webkit-tap-highlight-color:transparent; z-index:10;">
<div style="position:absolute; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.3);"></div>
<div id="$playIconId" style="position:relative;z-index:2;">$svgPlay</div>
</div>
<div style="flex:1; height:100%; display:flex; flex-direction:column; justify-content:center; padding:0 12px; box-sizing:border-box;">
<div style="width:100%; height:18px; overflow:hidden; position:relative; margin-bottom:6px;">
<div id="$lyricsId" style="width:100%; transition:transform 0.3s ease; text-align:center;">
<div style="height:18px; line-height:18px; font-size:12px; color:var(--color-huise);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">$initLyricText</div>
</div>
</div>
<div style="display:flex; align-items:center; gap:4px; width:100%; box-sizing:border-box; position:relative;">
<div style="font-size:10px; color:var(--color-huise); width:36px; flex-shrink:0; text-align:center; line-height:1;">
<span class="music-current-time" data-index="$musicIndex">00:00</span>
</div>
<div style="flex:1; position:relative; height:3px; padding:0 8px; box-sizing:border-box;">
<div style="height:100%; background:var(--bj-cifuse); border-radius:2px; cursor:pointer; width:100%;" class="music-progress-bar" data-index="$musicIndex">
<div class="music-progress" style="width:0%; height:100%; background:var(--bj-fuse); border-radius:2px; transition:width 0.1s linear;"></div>
</div>
<span id="$basketballId" style="position:absolute; top:-12px; left:8px; font-size:14px; opacity:0.9; pointer-events:none; z-index:5; transform:translateX(-50%);">🏀</span>
</div>
<div style="font-size:10px; color:var(--color-huise); width:36px; flex-shrink:0; text-align:center; line-height:1;">
<span class="music-duration" data-index="$musicIndex">00:00</span>
</div>
<div style="display:flex; align-items:center; gap:3px; width:50px; flex-shrink:0; margin-left:auto; justify-content:flex-end;">
<div id="$volumeIconId" style="color:var(--color-huise); cursor:pointer;">$svgVolume</div>
<div style="width:28px; height:3px; background:var(--bj-cifuse); border-radius:2px; cursor:pointer;">
<div class="music-volume-bar" data-index="$musicIndex" style="width:80%; height:100%; background:var(--bj-fuse); border-radius:2px; transition:width 0.1s linear;"></div>
</div>
</div>
</div>
</div>
</div>
$listHtml
</div>
<script>
(function() {
const player = document.getElementById('$playerId');
const playBtn = document.querySelector('.music-play-btn[data-index="$musicIndex"]');
const playIcon = document.getElementById('$playIconId');
const progressBar = document.querySelector('.music-progress-bar[data-index="$musicIndex"]');
const progress = progressBar ? progressBar.querySelector('.music-progress') : null;
const currentTimeEl = document.querySelector('.music-current-time[data-index="$musicIndex"]');
const durationEl = document.querySelector('.music-duration[data-index="$musicIndex"]');
const volumeBar = document.querySelector('.music-volume-bar[data-index="$musicIndex"]');
const volumeIcon = document.getElementById('$volumeIconId');
const lyricsEl = document.getElementById('$lyricsId');
const basketball = document.getElementById('$basketballId');
const listEl = document.getElementById('$listId');
const songName = $songNameJson;
const lyricsData = $lyricsJson;
const playlist = $playlistJson;
const isPlaylist = playlist.length > 0;
const svgPlay = '$svgPlay';
const svgPause = '$svgPause';
const svgVolume = '$svgVolume';
const svgVolumeMute = '$svgVolumeMute';
let currentLine = 0;
let isMetadataLoaded = false;
let currentSongIndex = 0;
let durationCache = {};
const formatTime = (s) => isNaN(s) || s < 0 ? "00:00" : Math.floor(s/60).toString().padStart(2,"0") + ":" + Math.floor(s%60).toString().padStart(2,"0");
let html = "<div style='height:18px;line-height:18px;font-size:12px;color:var(--color-huise);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;'>" + songName + "</div>";
if(lyricsData && lyricsData.length){
lyricsData.forEach(item => {
html += "<div style='height:18px;line-height:18px;font-size:12px;color:var(--color-huise);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;'>" + item.text + "</div>";
});
}
lyricsEl.innerHTML = html;
const setDuration = () => {
if(isMetadataLoaded) return;
if(player.duration && player.duration > 0){
durationEl.textContent = formatTime(player.duration);
isMetadataLoaded = true;
}
};
function switchSong(index) {
if (!playlist[index]) return;
currentSongIndex = index;
const song = playlist[index];
player.src = song.url;
isMetadataLoaded = false;
playBtn.style.backgroundImage = "url('" + (song.cover || '$defaultCover') + "')";
const lrcData = song.lyrics || [];
let lrcHtml = "<div style='height:18px;line-height:18px;font-size:12px;color:var(--color-huise);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;'>" + song.name + "</div>";
lrcData.forEach(item => {
lrcHtml += "<div style='height:18px;line-height:18px;font-size:12px;color:var(--color-huise);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;'>" + item.text + "</div>";
});
lyricsEl.innerHTML = lrcHtml;
currentLine = 0;
lyricsEl.style.transform = "translateY(0)";
progress.style.width = "0%";
basketball.style.left = "8px";
currentTimeEl.textContent = "00:00";
document.querySelectorAll("#$listId .music-list-item").forEach((item, i) => {
item.style.cssText = "padding:8px 10px; margin:4px 0; border-radius:4px;cursor:pointer; font-size:13px; color:var(--color-huise);transition:all 0.2s; display:flex; align-items:center; justify-content:space-between; gap:8px;";
if (i === index) {
item.style.background = "rgba(166,215,69,0.1)";
item.style.color = "#a6d745";
}
});
player.load();
player.play();
playIcon.innerHTML = svgPause;
}
if (isPlaylist && listEl) {
document.querySelectorAll("#$listId .music-list-item").forEach((item, index) => {
item.addEventListener('click', () => {
pauseAllOthers();
switchSong(index);
});
});
}
player.addEventListener('ended', () => {
if (isPlaylist && currentSongIndex < playlist.length - 1) {
switchSong(currentSongIndex + 1);
} else {
playIcon.innerHTML = svgPlay;
progress.style.width = "0%";
currentTimeEl.textContent = "00:00";
basketball.style.left = "8px";
lyricsEl.style.transform = "translateY(0)";
currentLine = 0;
}
});
player.addEventListener("loadedmetadata", setDuration);
player.addEventListener("canplay", setDuration);
player.addEventListener("loadeddata", setDuration);
setTimeout(setDuration, 800);
player.volume = 0.8;
const pauseAllOthers = () => {
document.querySelectorAll(".single-music-player").forEach(audio => {
if (audio !== player && !audio.paused) {
audio.pause();
const idx = audio.closest(".custom-music-player").dataset.musicIndex;
const icon = document.getElementById("play-icon-" + idx);
if (icon) icon.innerHTML = svgPlay;
}
});
};
playBtn.addEventListener("click", () => {
if (player.paused) {
pauseAllOthers();
player.play();
playIcon.innerHTML = svgPause;
} else {
player.pause();
playIcon.innerHTML = svgPlay;
}
});
player.addEventListener("timeupdate", () => {
if(!isMetadataLoaded) setDuration();
const pct = (player.currentTime / (player.duration || 1)) * 100;
progress.style.width = Math.min(100, pct) + "%";
currentTimeEl.textContent = formatTime(player.currentTime);
basketball.style.left = (8 + (progressBar.offsetWidth * pct / 100)) + "px";
let activeLine = 0;
const currentLyrics = isPlaylist ? playlist[currentSongIndex].lyrics : lyricsData;
if(currentLyrics && currentLyrics.length){
for (let i = 0; i < currentLyrics.length; i++) {
if (player.currentTime >= currentLyrics[i].time - 0.3) activeLine = i + 1;
}
}
if (activeLine !== currentLine) {
currentLine = activeLine;
lyricsEl.style.transform = "translateY(-" + (currentLine * 18) + "px)";
}
});
progressBar.addEventListener("click", (e) => {
if(!player.duration) return;
player.currentTime = (e.offsetX / progressBar.offsetWidth) * player.duration;
});
const updateVolume = (v) => {
player.volume = Math.max(0, Math.min(1, v));
volumeBar.style.width = (v * 100) + "%";
volumeIcon.innerHTML = player.volume <= 0 ? svgVolumeMute : svgVolume;
};
volumeBar.parentElement.addEventListener("click", (e) => {
updateVolume(e.offsetX / volumeBar.parentElement.offsetWidth);
});
volumeIcon.addEventListener("click", () => {
updateVolume(player.volume > 0 ? 0 : 0.8);
});
if(isPlaylist && playlist.length){
playlist.forEach((song,idx)=>{
const audio = new Audio();
audio.src = song.url;
audio.preload = "metadata";
audio.onloadedmetadata = ()=>{
durationCache[idx] = audio.duration;
const item = document.querySelector("#$listId .music-list-item[data-index='"+idx+"'] .list-duration");
if(item) item.textContent = formatTime(audio.duration);
};
});
}
})();
</script>
HTML;
return $playerHtml;
}, $content);
return $content;
}
function is_absolute_path($path) {
if (empty($path)) return false;
return str_starts_with($path, '/') || preg_match('/^[A-Za-z]:\\\\/', $path);
}
function getSingleMusicForShortcode($name = '') {
$MUSIC_ROOT = __TYPECHO_ROOT_DIR__ . '/usr/uploads/';
$MUSIC_DIR = $MUSIC_ROOT . 'music_files/';
$LYRIC_DIR = $MUSIC_ROOT . 'music_lyrics/';
$ALLOWED_EXT = ['mp3'];
$music = [];
$searchName = trim($name);
if (empty($searchName) || !is_dir($MUSIC_DIR)) return $music;
clearstatcache();
$rootDh = opendir($MUSIC_DIR);
if (!$rootDh) return $music;
while (($monthFolder = readdir($rootDh)) !== false) {
if (!preg_match('/^\d{6}$/', $monthFolder)) continue;
$dir = rtrim($MUSIC_DIR, '/') . '/' . $monthFolder . '/';
if (!is_dir($dir)) continue;
$dh = opendir($dir);
while (($file = readdir($dh)) !== false) {
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
if (!in_array($ext, $ALLOWED_EXT)) continue;
$fname = pathinfo($file, PATHINFO_FILENAME);
if (stripos($fname, $searchName) !== false) {
$lyrics = '';
if (is_dir($LYRIC_DIR)) {
foreach (scandir($LYRIC_DIR) as $d) {
$lrc = $LYRIC_DIR.$d.'/'.$fname.'.lrc';
if (file_exists($lrc)) {
$lyrics = trim(file_get_contents($lrc));
$lyrics = preg_replace('/^\xEF\xBB\xBF/', '', $lyrics);
break;
}
}
}
$cover = '';
$imgJpg = $dir . $fname . '.jpg';
$imgPng = $dir . $fname . '.png';
if (file_exists($imgJpg)) {
$cover = str_replace(__TYPECHO_ROOT_DIR__, '', $imgJpg);
} elseif (file_exists($imgPng)) {
$cover = str_replace(__TYPECHO_ROOT_DIR__, '', $imgPng);
}
$siteUrl = rtrim(Helper::options()->siteUrl, '/');
$url = $siteUrl.str_replace(__TYPECHO_ROOT_DIR__, '', $dir.$file);
closedir($dh); closedir($rootDh);
return [
'name' => $fname,
'url' => $url,
'cover' => $cover,
'lyrics' => $lyrics
];
}
}
closedir($dh);
}
closedir($rootDh);
return $music;
}
function getMusicByMp3($path) {
$music = [];
$path = trim($path);
if (empty($path)) return $music;
$ALLOWED_EXT = ['mp3'];
$siteUrl = rtrim(Helper::options()->siteUrl, '/');
$rootDir = __TYPECHO_ROOT_DIR__;
if (str_starts_with($path, 'http://') || str_starts_with($path, 'https://')) {
return [
'name' => '',
'url' => $path,
'cover' => '',
'lyrics' => ''
];
}
$absPath = !is_absolute_path($path) ? $rootDir.'/'.ltrim($path, '/') : $path;
if (!@file_exists($absPath)) $absPath = realpath($absPath) ?: $absPath;
if (!@file_exists($absPath) || !in_array(strtolower(pathinfo($absPath, PATHINFO_EXTENSION)), $ALLOWED_EXT)) return $music;
$fname = pathinfo($absPath, PATHINFO_FILENAME);
$dir = pathinfo($absPath, PATHINFO_DIRNAME);
$cover = '';
$imgJpg = $dir . '/' . $fname . '.jpg';
$imgPng = $dir . '/' . $fname . '.png';
if (file_exists($imgJpg)) {
$cover = str_replace($rootDir, '', $imgJpg);
} elseif (file_exists($imgPng)) {
$cover = str_replace($rootDir, '', $imgPng);
}
$url = $siteUrl.str_replace($rootDir, '', $absPath);
$lrc = $dir.'/'.$fname.'.lrc';
$lyrics = file_exists($lrc) ? trim(file_get_contents($lrc)) : '';
$lyrics = preg_replace('/^\xEF\xBB\xBF/', '', $lyrics);
return [
'name' => $fname,
'url' => $url,
'cover' => $cover,
'lyrics' => $lyrics
];
}
function parseLrcLyrics($lrcContent) {
$lyrics = [];
if (empty($lrcContent)) return $lyrics;
$lines = preg_split('/\r\n|\r|\n/', $lrcContent);
foreach ($lines as $line) {
if (preg_match('/\[(\d+):(\d+(?:\.\d+)?)\](.*)/', trim($line), $m)) {
$text = trim($m[3]);
if ($text) $lyrics[] = ['time' => (int)$m[1]*60 + (float)$m[2], 'text' => $text];
}
}
usort($lyrics, fn($a,$b) => $a['time'] - $b['time']);
return $lyrics;
}
function registerMusicShortcode() {
if (defined('__TYPECHO_ROOT_DIR__') && class_exists('Typecho\Plugin')) {
Typecho\Plugin::factory('Widget_Abstract_Contents')->contentEx = 'parseMusicShortcode';
Typecho\Plugin::factory('Widget_Abstract_Contents')->excerptEx = 'parseMusicShortcode';
}
}现在不知道是哪个环节出了问题~求教各位大佬。俺Q6271945 问题解决,奖励大佬50话费。
评论: 12 | 查看: 142
上一篇
没有了 
应该解决了吧?
没没没,大佬看看怎么整。
厉害 速度解决了。
今天没整,晚上看看邮箱,看看我东哥怎么说的先。
我发你邮箱了试试看,没有重新给封面 img 赋值 src
单独给歌曲添加图片路径会显示,现在就是调用音乐盒中歌曲时封面获取不到,不知道怎么整了。
明白你的意思,列表播放无法加载封面,切歌也是无法加载!修改后的发到srgtepl📧aliyun.com里面!
嗯嗯嗯,意思是这样。先睡觉,明天干完活回来在试下。
晚安!
咋发不出代码,qq也搜索不到!
打开了,东哥加加。
试试看