AI摘要介绍
本文介绍一款免费开源的音乐播放器,它基于MetingApi驱动,可适配多种网站。该播放器解决了收费、歌曲添加繁琐及部分音乐无法播放等问题,并支持替换API地址以保持稳定运行。
本站同款音乐播放器免费开源
还在因网站播放器是收费的而烦恼?
还在因播放器歌曲太难添加而耗时间?
还在因某些音乐是收费的导致不能播放而痛苦?
现在我将开源本站的音乐播放器,理论上是适用所有网站的,可以添加到自己的其他网站试试
本播放器基于MetingApi作为核心驱动,所以即使这个MetingApi的地址失效了,还可以换其他的(搜索引擎一搜一大堆),例如如果https://music.3e0.cn/ 这个api失效了,只需要搜索https://music.3e0.cn/ 并替换成其他api例如https://xxx.com
歌单ID也可以换
本播放器修复了大量BUG,已经很稳定了
遇到其他网站和本网站(blog.3e0.cn)一模一的播放器都是出自本网站所开源的
在下面代码中播放器的歌单是网易云热歌榜
以下是代码
纯文本
</div><div><br></div><div><style></div><div>.meting-player-container * {</div><div> margin: 0;</div><div> padding: 0;</div><div> box-sizing: border-box;</div><div> font-family: 'Microsoft YaHei', sans-serif;</div><div>}</div><div><br></div><div>.meting-player-container {</div><div> position: fixed;</div><div> left: 20px;</div><div> bottom: 70px;</div><div> z-index: 1000;</div><div> width: 360px;</div><div> transition: all 0.3s ease;</div><div>}</div><div><br></div><div>.meting-player-container.minimized {</div><div> width: 50px;</div><div> height: 50px;</div><div>}</div><div><br></div><div>.meting-player-main {</div><div> background: rgba(60, 66, 64, 0.9);</div><div> border-radius: 12px;</div><div> overflow: hidden;</div><div> box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);</div><div> color: white;</div><div> display: flex;</div><div> flex-direction: column;</div><div> transition: all 0.3s ease;</div><div>}</div><div><br></div><div>.minimized .meting-player-main {</div><div> height: 50px;</div><div> width: 50px;</div><div> border-radius: 50%;</div><div> display: flex;</div><div> align-items: center;</div><div> justify-content: center;</div><div>}</div><div><br></div><div>.meting-album-cover {</div><div> display: none;</div><div> width: 40px;</div><div> height: 40px;</div><div> border-radius: 50%;</div><div> object-fit: cover;</div><div> animation: meting-rotate 10s linear infinite;</div><div> animation-play-state: paused;</div><div>}</div><div><br></div><div>.minimized .meting-album-cover {</div><div> display: block;</div><div>}</div><div><br></div><div>.playing .meting-album-cover {</div><div> animation-play-state: running;</div><div>}</div><div><br></div><div>@keyframes meting-rotate {</div><div> 100% {</div><div> transform: rotate(360deg);</div><div> }</div><div>}</div><div><br></div><div>.meting-player-header {</div><div> display: flex;</div><div> align-items: center;</div><div> padding: 12px 15px;</div><div> border-bottom: 1px solid rgba(255, 255, 255, 0.1);</div><div>}</div><div><br></div><div>.minimized .meting-player-header {</div><div> display: none;</div><div>}</div><div><br></div><div>.meting-song-info {</div><div> flex: 1;</div><div> overflow: hidden;</div><div>}</div><div><br></div><div>.meting-song-title {</div><div> font-size: 14px;</div><div> white-space: nowrap;</div><div> overflow: hidden;</div><div> text-overflow: ellipsis;</div><div>}</div><div><br></div><div>.meting-song-artist {</div><div> font-size: 12px;</div><div> opacity: 0.8;</div><div> white-space: nowrap;</div><div> overflow: hidden;</div><div> text-overflow: ellipsis;</div><div>}</div><div><br></div><div>.meting-player-controls {</div><div> display: flex;</div><div> flex-direction: column;</div><div> padding: 15px;</div><div>}</div><div><br></div><div>.minimized .meting-player-controls {</div><div> display: none;</div><div>}</div><div><br></div><div>.meting-progress-top {</div><div> display: flex;</div><div> align-items: center;</div><div> margin-bottom: 15px;</div><div> width: 100%;</div><div>}</div><div><br></div><div>.meting-progress-container {</div><div> flex: 1;</div><div> height: 12px;</div><div> background: rgba(255, 255, 255, 0.3);</div><div> border-radius: 6px;</div><div> margin-right: 10px;</div><div> cursor: pointer;</div><div> position: relative;</div><div>}</div><div><br></div><div>.meting-progress-bar {</div><div> height: 100%;</div><div> background: #1db954;</div><div> border-radius: 6px;</div><div> width: 0%;</div><div>}</div><div><br></div><div>.meting-time-display {</div><div> font-size: 12px;</div><div> opacity: 0.8;</div><div> min-width: 80px;</div><div> text-align: center;</div><div>}</div><div><br></div><div>.meting-controls-bottom {</div><div> display: flex;</div><div> align-items: center;</div><div> justify-content: space-between;</div><div> width: 100%;</div><div>}</div><div><br></div><div>.meting-control-buttons {</div><div> display: flex;</div><div> align-items: center;</div><div>}</div><div><br></div><div>.meting-control-btn,</div><div>.meting-volume-btn,</div><div>.meting-toggle-player,</div><div>.meting-lyrics-btn,</div><div>.meting-playlist-btn,</div><div>.meting-mode-btn {</div><div> -webkit-tap-highlight-color: transparent;</div><div> outline: none;</div><div>}</div><div><br></div><div>.meting-control-btn {</div><div> background: none;</div><div> border: none;</div><div> color: white;</div><div> font-size: 20px;</div><div> cursor: pointer;</div><div> margin: 0 5px;</div><div> transition: all 0.2s;</div><div> width: 32px;</div><div> height: 32px;</div><div> display: flex;</div><div> align-items: center;</div><div> justify-content: center;</div><div>}</div><div><br></div><div>.meting-control-btn:hover {</div><div> color: #1db954;</div><div>}</div><div><br></div><div>.meting-play-pause {</div><div> font-size: 32px;</div><div> width: 40px;</div><div> height: 40px;</div><div>}</div><div><br></div><div>.meting-volume-container {</div><div> display: flex;</div><div> align-items: center;</div><div>}</div><div><br></div><div>.meting-volume-btn {</div><div> background: none;</div><div> border: none;</div><div> color: white;</div><div> font-size: 18px;</div><div> cursor: pointer;</div><div> width: 24px;</div><div> height: 24px;</div><div> display: flex;</div><div> align-items: center;</div><div> justify-content: center;</div><div>}</div><div><br></div><div>.meting-volume-slider {</div><div> width: 80px;</div><div> margin-left: 5px;</div><div> -webkit-appearance: none;</div><div> height: 4px;</div><div> background: rgba(255, 255, 255, 0.3);</div><div> border-radius: 2px;</div><div> outline: none;</div><div> -webkit-tap-highlight-color: transparent;</div><div>}</div><div><br></div><div>.meting-volume-slider::-webkit-slider-thumb {</div><div> -webkit-appearance: none;</div><div> width: 12px;</div><div> height: 12px;</div><div> border-radius: 50%;</div><div> background: #fff;</div><div> cursor: pointer;</div><div>}</div><div><br></div><div>.meting-toggle-player {</div><div> position: absolute;</div><div> top: -10px;</div><div> right: -10px;</div><div> width: 24px;</div><div> height: 24px;</div><div> border-radius: 50%;</div><div> background: #1db954;</div><div> color: white;</div><div> display: flex;</div><div> align-items: center;</div><div> justify-content: center;</div><div> cursor: pointer;</div><div> box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);</div><div> border: none;</div><div> font-size: 14px;</div><div> transition: all 0.2s;</div><div>}</div><div><br></div><div>.meting-toggle-player:hover {</div><div> transform: scale(1.1);</div><div>}</div><div><br></div><div>.meting-lyrics-container {</div><div> position: fixed;</div><div> bottom: 0;</div><div> left: 0;</div><div> right: 0;</div><div> text-align: center;</div><div> padding: 10px;</div><div> background: rgba(0, 0, 0, 0.5);</div><div> color: white;</div><div> font-size: 16px;</div><div> z-index: 900;</div><div> transition: all 0.3s;</div><div> line-height: 1.5;</div><div>}</div><div><br></div><div>.meting-lyrics-line {</div><div> margin: 5px 0;</div><div> opacity: 0.5;</div><div> transition: all 0.3s;</div><div>}</div><div><br></div><div>.meting-lyrics-line.active {</div><div> opacity: 1;</div><div> color: #1db954;</div><div> font-weight: bold;</div><div> transform: scale(1.05);</div><div>}</div><div><br></div><div>.meting-playlist-btn {</div><div> background: none;</div><div> border: none;</div><div> color: white;</div><div> font-size: 20px;</div><div> cursor: pointer;</div><div> margin-left: 10px;</div><div> width: 24px;</div><div> height: 24px;</div><div> display: flex;</div><div> align-items: center;</div><div> justify-content: center;</div><div>}</div><div><br></div><div>.meting-lyrics-btn {</div><div> background: none;</div><div> border: none;</div><div> color: white;</div><div> font-size: 16px;</div><div> cursor: pointer;</div><div> margin-left: 10px;</div><div> width: 24px;</div><div> height: 24px;</div><div> display: flex;</div><div> align-items: center;</div><div> justify-content: center;</div><div> font-weight: bold;</div><div>}</div><div><br></div><div>.meting-lyrics-btn.active {</div><div> color: #1db954;</div><div>}</div><div><br></div><div>/* 重新设计播放列表样式 - 更美观的电脑端播放列表 */</div><div>.meting-playlist-container {</div><div> position: absolute;</div><div> bottom: calc(100% + 10px);</div><div> left: 0;</div><div> right: 0;</div><div> background: rgba(60, 66, 64, 0.9);</div><div> backdrop-filter: blur(10px);</div><div> border-radius: 12px;</div><div> max-height: 400px;</div><div> overflow-y: auto;</div><div> display: none;</div><div> padding: 15px;</div><div> box-shadow: 0 -5px 25px rgba(0, 0, 0, 0.4);</div><div> border: 1px solid rgba(255, 255, 255, 0.1);</div><div> scrollbar-width: thin;</div><div> scrollbar-color: #1db954 rgba(255, 255, 255, 0.1);</div><div>}</div><div><br></div><div>.meting-playlist-container::-webkit-scrollbar {</div><div> width: 6px;</div><div>}</div><div><br></div><div>.meting-playlist-container::-webkit-scrollbar-track {</div><div> background: rgba(255, 255, 255, 0.05);</div><div> border-radius: 3px;</div><div>}</div><div><br></div><div>.meting-playlist-container::-webkit-scrollbar-thumb {</div><div> background-color: #1db954;</div><div> border-radius: 3px;</div><div>}</div><div>/* 本播放器来源blog.3e0.cn 使用请保留本标识 */</div><div>.meting-playlist-container.show {</div><div> display: block;</div><div>}</div><div><br></div><div>.meting-playlist-header {</div><div> display: flex;</div><div> justify-content: space-between;</div><div> align-items: center;</div><div> margin-bottom: 15px;</div><div> padding-bottom: 10px;</div><div> border-bottom: 1px solid rgba(255, 255, 255, 0.1);</div><div>}</div><div><br></div><div>.meting-playlist-title {</div><div> font-size: 16px;</div><div> font-weight: bold;</div><div> color: #fff;</div><div>}</div><div><br></div><div>.meting-playlist-count {</div><div> font-size: 13px;</div><div> opacity: 0.7;</div><div>}</div><div><br></div><div>.meting-playlist-items {</div><div> display: flex;</div><div> flex-direction: column;</div><div> gap: 8px;</div><div>}</div><div><br></div><div>.meting-playlist-item {</div><div> padding: 12px 15px;</div><div> border-radius: 8px;</div><div> cursor: pointer;</div><div> display: flex;</div><div> align-items: center;</div><div> transition: all 0.2s ease;</div><div> background: rgba(255, 255, 255, 0.03);</div><div> -webkit-tap-highlight-color: transparent;</div><div> outline: none;</div><div>}</div><div><br></div><div>.meting-playlist-item:hover {</div><div> background: rgba(29, 185, 84, 0.15);</div><div> transform: translateY(-2px);</div><div>}</div><div><br></div><div>.meting-playlist-item.playing {</div><div> background: rgba(29, 185, 84, 0.2);</div><div> box-shadow: 0 4px 12px rgba(29, 185, 84, 0.2);</div><div>}</div><div><br></div><div>.meting-playlist-item.playing .meting-playlist-item-index {</div><div> color: #1db954;</div><div> font-weight: bold;</div><div>}</div><div><br></div><div>.meting-playlist-item.playing .meting-playlist-item-title {</div><div> color: #1db954;</div><div>}</div><div><br></div><div>.meting-playlist-item-index {</div><div> margin-right: 15px;</div><div> font-size: 14px;</div><div> opacity: 0.8;</div><div> min-width: 24px;</div><div> text-align: center;</div><div> transition: all 0.2s ease;</div><div>}</div><div><br></div><div>.meting-playlist-item-info {</div><div> flex: 1;</div><div> overflow: hidden;</div><div> min-width: 0;</div><div>}</div><div><br></div><div>.meting-playlist-item-title {</div><div> font-size: 14px;</div><div> font-weight: 500;</div><div> white-space: nowrap;</div><div> overflow: hidden;</div><div> text-overflow: ellipsis;</div><div> margin-bottom: 3px;</div><div> transition: all 0.2s ease;</div><div>}</div><div><br></div><div>.meting-playlist-item-artist {</div><div> font-size: 12px;</div><div> opacity: 0.7;</div><div> white-space: nowrap;</div><div> overflow: hidden;</div><div> text-overflow: ellipsis;</div><div>}</div><div><br></div><div>.meting-playlist-item-duration {</div><div> font-size: 12px;</div><div> opacity: 0.6;</div><div> margin-left: 10px;</div><div> min-width: 40px;</div><div> text-align: right;</div><div>}</div><div><br></div><div>.meting-mode-btn {</div><div> background: none;</div><div> border: none;</div><div> color: white;</div><div> font-size: 16px;</div><div> cursor: pointer;</div><div> margin-left: 5px;</div><div> width: 24px;</div><div> height: 24px;</div><div> display: flex;</div><div> align-items: center;</div><div> justify-content: center;</div><div>}</div><div><br></div><div>@media screen and (max-width: 768px) {</div><div> .meting-player-container {</div><div> left: 10px;</div><div> right: auto;</div><div> bottom: 20px;</div><div> width: auto;</div><div> max-width: 320px;</div><div> margin: 0;</div><div> }</div><div> </div><div> .meting-player-main {</div><div> border-radius: 10px;</div><div> }</div><div> </div><div> .meting-player-header {</div><div> padding: 10px;</div><div> }</div><div> </div><div> .meting-song-title {</div><div> font-size: 13px;</div><div> }</div><div> </div><div> .meting-song-artist {</div><div> font-size: 11px;</div><div> }</div><div> </div><div> .meting-player-controls {</div><div> padding: 10px;</div><div> }</div><div> </div><div> .meting-progress-top {</div><div> flex-direction: row;</div><div> align-items: center;</div><div> margin-bottom: 10px;</div><div> }</div><div> </div><div> .meting-time-display {</div><div> margin-top: 0;</div><div> min-width: auto;</div><div> text-align: right;</div><div> font-size: 11px;</div><div> margin-left: 10px;</div><div> }</div><div> </div><div> .meting-controls-bottom {</div><div> flex-direction: row;</div><div> gap: 5px;</div><div> }</div><div> </div><div> .meting-volume-container {</div><div> width: auto;</div><div> justify-content: flex-end;</div><div> }</div><div> </div><div> .meting-volume-slider {</div><div> flex: 1;</div><div> max-width: 80px;</div><div> }</div><div> </div><div> .meting-control-btn {</div><div> font-size: 18px;</div><div> margin: 0 3px;</div><div> width: 28px;</div><div> height: 28px;</div><div> }</div><div> </div><div> .meting-play-pause {</div><div> font-size: 28px;</div><div> width: 36px;</div><div> height: 36px;</div><div> }</div><div> </div><div> .meting-toggle-player {</div><div> top: -8px;</div><div> right: -8px;</div><div> width: 20px;</div><div> height: 20px;</div><div> font-size: 12px;</div><div> }</div><div> </div><div> .meting-lyrics-container {</div><div> font-size: 14px;</div><div> padding: 8px;</div><div> z-index: 800;</div><div> line-height: 1.5;</div><div> }</div><div> </div><div> /* 手机端播放列表样式 */</div><div> .meting-playlist-container {</div><div> max-height: 200px;</div><div> border-radius: 10px;</div><div> padding: 10px;</div><div> bottom: calc(100% + 8px);</div><div> background: rgba(60, 66, 64, 0.9);</div><div> }</div><div> </div><div> .meting-playlist-header {</div><div> margin-bottom: 10px;</div><div> padding-bottom: 8px;</div><div> }</div><div> </div><div> .meting-playlist-title {</div><div> font-size: 14px;</div><div> }</div><div> </div><div> .meting-playlist-count {</div><div> font-size: 11px;</div><div> }</div><div> </div><div> .meting-playlist-item {</div><div> padding: 10px 12px;</div><div> border-radius: 6px;</div><div> }</div><div> </div><div> .meting-playlist-item-index {</div><div> margin-right: 12px;</div><div> font-size: 12px;</div><div> min-width: 20px;</div><div> }</div><div> </div><div> .meting-playlist-item-title {</div><div> font-size: 13px;</div><div> }</div><div> </div><div> .meting-playlist-item-artist {</div><div> font-size: 11px;</div><div> }</div><div> </div><div> .meting-playlist-item-duration {</div><div> font-size: 11px;</div><div> min-width: 35px;</div><div> }</div><div> </div><div> .meting-lyrics-container:not([style*="display: none"]) {</div><div> height: auto;</div><div> }</div><div> </div><div> .meting-lyrics-container[style*="display: none"] ~ .meting-player-container {</div><div> bottom: 10px;</div><div> }</div><div>}</div><div><br></div><div>/* 小屏手机适配 - 修改为左下角位置 */</div><div>@media screen and (max-width: 480px) {</div><div> .meting-player-container {</div><div> left: 5px;</div><div> right: auto;</div><div> bottom: 10px;</div><div> max-width: 300px;</div><div> }</div><div>/* 这个播放器来源blog.3e0.cn 使用请保留本标识 */ </div><div> .meting-player-header {</div><div> padding: 8px;</div><div> }</div><div> </div><div> .meting-song-title {</div><div> font-size: 12px;</div><div> }</div><div> </div><div> .meting-song-artist {</div><div> font-size: 10px;</div><div> }</div><div> </div><div> .meting-control-btn {</div><div> font-size: 16px;</div><div> margin: 0 2px;</div><div> width: 24px;</div><div> height: 24px;</div><div> }</div><div> </div><div> .meting-play-pause {</div><div> font-size: 24px;</div><div> width: 32px;</div><div> height: 32px;</div><div> }</div><div> </div><div> .meting-lyrics-btn,</div><div> .meting-playlist-btn,</div><div> .meting-mode-btn {</div><div> font-size: 16px;</div><div> margin-left: 5px;</div><div> width: 20px;</div><div> height: 20px;</div><div> }</div><div> </div><div> .meting-time-display {</div><div> font-size: 10px;</div><div> }</div><div> </div><div> .meting-lyrics-container {</div><div> font-size: 13px;</div><div> padding: 5px;</div><div> line-height: 1.5;</div><div> }</div><div> </div><div> .meting-volume-slider {</div><div> max-width: 60px;</div><div> }</div><div> </div><div> /* 小屏手机播放列表样式 */</div><div> .meting-playlist-container {</div><div> max-height: 180px;</div><div> padding: 8px;</div><div> bottom: calc(100% + 5px);</div><div> border-radius: 8px;</div><div> background: rgba(60, 66, 64, 0.9);</div><div> }</div><div> </div><div> .meting-playlist-item {</div><div> padding: 8px 10px;</div><div> }</div><div> </div><div> .meting-playlist-item-index {</div><div> margin-right: 10px;</div><div> font-size: 11px;</div><div> min-width: 18px;</div><div> }</div><div> </div><div> .meting-playlist-item-title {</div><div> font-size: 12px;</div><div> }</div><div> </div><div> .meting-playlist-item-artist {</div><div> font-size: 10px;</div><div> }</div><div> </div><div> .meting-playlist-item-duration {</div><div> display: none;</div><div> }</div><div>}</div><div></style></div><div><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"></div><div><div class="meting-player-container"></div><div> <div class="meting-player-main"></div><div> <img class="meting-album-cover" src="" alt="专辑封面"></div><div> <div class="meting-player-header"></div><div> <div class="meting-song-info"></div><div> <div class="meting-song-title">正在加载...</div></div><div> <div class="meting-song-artist">请稍候</div></div><div> </div></div><div> <button class="meting-lyrics-btn">词</button></div><div> <button class="meting-playlist-btn"><i class="fa fa-list"></i></button></div><div> <button class="meting-mode-btn"><i class="fa fa-random"></i></button></div><div> </div></div><div> <div class="meting-player-controls"></div><div> <div class="meting-progress-top"></div><div> <div class="meting-progress-container"></div><div> <div class="meting-progress-bar"></div></div><div> </div></div><div> <div class="meting-time-display">00:00 / 00:00</div></div><div> </div></div><div> <div class="meting-controls-bottom"></div><div> <div class="meting-control-buttons"></div><div> <button class="meting-control-btn meting-prev-btn"><i class="fa fa-step-backward"></i></button></div><div> <button class="meting-control-btn meting-play-pause"><i class="fa fa-play"></i></button></div><div> <button class="meting-control-btn meting-next-btn"><i class="fa fa-step-forward"></i></button></div><div> </div></div><div> <div class="meting-volume-container"></div><div> <button class="meting-volume-btn"><i class="fa fa-volume-up"></i></button></div><div> <input type="range" class="meting-volume-slider" min="0" max="100" value="80"></div><div> </div></div><div> </div></div><div> </div></div><div> <div class="meting-playlist-container"></div><div> <div class="meting-playlist-header"></div><div> <div class="meting-playlist-title">播放列表</div></div><div> <div class="meting-playlist-count">0 首歌曲</div></div><div> </div></div><div> <div class="meting-playlist-items"></div></div><div> </div></div><div> </div></div><div> <button class="meting-toggle-player"><i class="fa fa-minus"></i></button></div><div></div></div><div><br></div><div><div class="meting-lyrics-container"></div><div> <div class="meting-lyrics-line">歌词加载中...</div></div><div></div></div><div><br></div><div><script></div><div>(function() {</div><div> // 播放器状态</div><div> const playerState = {</div><div> playlist: [],</div><div> currentIndex: 0,</div><div> isPlaying: false,</div><div> volume: 80,</div><div> currentTime: 0,</div><div> duration: 0,</div><div> mode: 'random',</div><div> minimized: false,</div><div> lyricsVisible: true,</div><div> lyricsAutoHide: true,</div><div> lastPlayedSong: null,</div><div> lastPlayedPosition: 0,</div><div> autoPlayBlocked: false,</div><div> lyrics: []</div><div> };</div><div><br></div><div> // DOM元素</div><div> const elements = {</div><div> playerContainer: document.querySelector('.meting-player-container'),</div><div> playerMain: document.querySelector('.meting-player-main'),</div><div> albumCover: document.querySelector('.meting-album-cover'),</div><div> songTitle: document.querySelector('.meting-song-title'),</div><div> songArtist: document.querySelector('.meting-song-artist'),</div><div> playPauseBtn: document.querySelector('.meting-play-pause'),</div><div> prevBtn: document.querySelector('.meting-prev-btn'),</div><div> nextBtn: document.querySelector('.meting-next-btn'),</div><div> progressContainer: document.querySelector('.meting-progress-container'),</div><div> progressBar: document.querySelector('.meting-progress-bar'),</div><div> timeDisplay: document.querySelector('.meting-time-display'),</div><div> volumeBtn: document.querySelector('.meting-volume-btn'),</div><div> volumeSlider: document.querySelector('.meting-volume-slider'),</div><div> togglePlayerBtn: document.querySelector('.meting-toggle-player'),</div><div> lyricsContainer: document.querySelector('.meting-lyrics-container'),</div><div> lyricsLine: document.querySelector('.meting-lyrics-line'),</div><div> playlistBtn: document.querySelector('.meting-playlist-btn'),</div><div> playlistContainer: document.querySelector('.meting-playlist-container'),</div><div> playlistItems: document.querySelector('.meting-playlist-items'),</div><div> playlistTitle: document.querySelector('.meting-playlist-title'),</div><div> playlistCount: document.querySelector('.meting-playlist-count'),</div><div> modeBtn: document.querySelector('.meting-mode-btn'),</div><div> lyricsBtn: document.querySelector('.meting-lyrics-btn')</div><div> };</div><div><br></div><div> // 音频元素</div><div> const audio = new Audio();</div><div> audio.volume = playerState.volume / 100;</div><div><br></div><div> // 新增:HTML实体解码函数,解决'显示问题</div><div> function decodeHtmlEntities(text) {</div><div> const textArea = document.createElement('textarea');</div><div> textArea.innerHTML = text;</div><div> return textArea.value;</div><div> }</div><div><br></div><div> // 从localStorage加载状态</div><div> function loadPlayerState() {</div><div> const savedState = localStorage.getItem('metingPlayerState');</div><div> if (savedState) {</div><div> const state = JSON.parse(savedState);</div><div> playerState.currentIndex = state.currentIndex || 0;</div><div> playerState.isPlaying = state.isPlaying || false;</div><div> playerState.volume = state.volume || 80;</div><div> playerState.currentTime = state.currentTime || 0;</div><div> playerState.mode = state.mode || 'random';</div><div> playerState.minimized = state.minimized || false;</div><div> playerState.lyricsVisible = state.lyricsVisible !== undefined ? state.lyricsVisible : true;</div><div> playerState.lyricsAutoHide = state.lyricsAutoHide !== undefined ? state.lyricsAutoHide : true;</div><div> playerState.lastPlayedSong = state.lastPlayedSong || null;</div><div> playerState.lastPlayedPosition = state.lastPlayedPosition || 0;</div><div> </div><div> // 应用保存的状态</div><div> audio.volume = playerState.volume / 100;</div><div> elements.volumeSlider.value = playerState.volume;</div><div> </div><div> if (playerState.minimized) {</div><div> elements.playerContainer.classList.add('minimized');</div><div> elements.togglePlayerBtn.innerHTML = '<i class="fa fa-plus"></i>';</div><div> }</div><div> </div><div> // 强制同步歌词按钮和显示状态</div><div> updateLyricsButton();</div><div> updateLyricsVisibility();</div><div> </div><div> updateModeButton();</div><div> updateVolumeIcon();</div><div> }</div><div> }</div><div><br></div><div> // 保存状态到localStorage</div><div> function savePlayerState() {</div><div> playerState.currentTime = audio.currentTime;</div><div> </div><div> if (playerState.playlist.length > 0 && playerState.currentIndex >= 0) {</div><div> const currentSong = playerState.playlist[playerState.currentIndex];</div><div> playerState.lastPlayedSong = {</div><div> id: currentSong.id || currentSong.name,</div><div> name: currentSong.name,</div><div> artist: currentSong.artist</div><div> };</div><div> playerState.lastPlayedPosition = audio.currentTime;</div><div> }</div><div> </div><div> localStorage.setItem('metingPlayerState', JSON.stringify(playerState));</div><div> }</div><div><br></div><div> // 更新歌词按钮状态</div><div> function updateLyricsButton() {</div><div> if (playerState.lyricsVisible) {</div><div> elements.lyricsBtn.classList.add('active');</div><div> } else {</div><div> elements.lyricsBtn.classList.remove('active');</div><div> }</div><div> }</div><div><br></div><div> // 修复歌词开启后播放不显示的问题:简化判断逻辑,确保播放时优先显示</div><div> function updateLyricsVisibility() {</div><div> // 只要歌词开启、未被自动播放阻止,且音频有资源,播放时就显示</div><div> const canShowLyrics = playerState.lyricsVisible && !playerState.autoPlayBlocked && audio.src;</div><div> if (playerState.isPlaying && canShowLyrics) {</div><div> elements.lyricsContainer.style.display = 'block';</div><div> adjustPlayerPosition();</div><div> } else {</div><div> elements.lyricsContainer.style.display = 'none';</div><div> resetPlayerPosition();</div><div> }</div><div> }</div><div><br></div><div> // 调整播放器位置(确保在歌词上方)</div><div> function adjustPlayerPosition() {</div><div> if (window.matchMedia('(max-width: 768px)').matches) {</div><div> const lyricsHeight = elements.lyricsContainer.offsetHeight;</div><div> elements.playerContainer.style.bottom = (lyricsHeight + 10) + 'px';</div><div> }</div><div> }</div><div><br></div><div> // 重置播放器位置(歌词隐藏时)</div><div> function resetPlayerPosition() {</div><div> if (window.matchMedia('(max-width: 768px)').matches) {</div><div> elements.playerContainer.style.bottom = '10px';</div><div> }</div><div> }</div><div><br></div><div> // 更新模式按钮</div><div> function updateModeButton() {</div><div> switch(playerState.mode) {</div><div> case 'random':</div><div> elements.modeBtn.innerHTML = '<i class="fa fa-random"></i>';</div><div> break;</div><div> case 'sequential':</div><div> elements.modeBtn.innerHTML = '<i class="fa fa-arrow-right"></i>';</div><div> break;</div><div> case 'loop':</div><div> elements.modeBtn.innerHTML = '<i class="fa fa-repeat"></i>';</div><div> break;</div><div> }</div><div> }</div><div><br></div><div> // 更新音量图标</div><div> function updateVolumeIcon() {</div><div> const volume = elements.volumeSlider.value;</div><div> let iconClass = 'fa-volume-up';</div><div> </div><div> if (volume == 0) {</div><div> iconClass = 'fa-volume-off';</div><div> } else if (volume < 33) {</div><div> iconClass = 'fa-volume-down';</div><div> }</div><div> </div><div> elements.volumeBtn.innerHTML = `<i class="fa ${iconClass}"></i>`;</div><div> }</div><div><br></div><div> // 从MetingAPI获取播放列表</div><div> async function fetchPlaylist() {</div><div> try {</div><div> const response = await fetch('https://music.3e0.cn/?server=netease&type=playlist&id=3778678');</div><div> const playlist = await response.json();</div><div> </div><div> if (Array.isArray(playlist) && playlist.length > 0) {</div><div> playerState.playlist = playlist;</div><div> renderPlaylist();</div><div> </div><div> // 检查是否有上次播放的歌曲记录</div><div> let targetIndex = 0;</div><div> let targetPosition = 0;</div><div> </div><div> if (playerState.lastPlayedSong) {</div><div> const foundIndex = playlist.findIndex(song => </div><div> (song.id && song.id === playerState.lastPlayedSong.id) || </div><div> (song.name === playerState.lastPlayedSong.name && song.artist === playerState.lastPlayedSong.artist)</div><div> );</div><div> </div><div> if (foundIndex !== -1) {</div><div> targetIndex = foundIndex;</div><div> targetPosition = playerState.lastPlayedPosition;</div><div> }</div><div> } else if (playerState.currentIndex >= 0 && playerState.currentIndex < playlist.length) {</div><div> targetIndex = playerState.currentIndex;</div><div> targetPosition = playerState.currentTime;</div><div> }</div><div> </div><div> loadSong(targetIndex, targetPosition);</div><div> </div><div> if (playerState.isPlaying) {</div><div> setTimeout(() => {</div><div> audio.play().catch(e => {</div><div> console.log('自动播放被阻止:', e);</div><div> playerState.autoPlayBlocked = true;</div><div> playerState.isPlaying = false;</div><div> updatePlayButtonState();</div><div> savePlayerState();</div><div> });</div><div> }, 500);</div><div> }</div><div> } else {</div><div> console.error('获取播放列表失败');</div><div> elements.songTitle.textContent = '获取播放列表失败';</div><div> }</div><div> } catch (error) {</div><div> console.error('获取播放列表出错:', error);</div><div> elements.songTitle.textContent = '获取播放列表出错';</div><div> }</div><div> }</div><div><br></div><div> // 渲染播放列表</div><div> function renderPlaylist() {</div><div> elements.playlistItems.innerHTML = '';</div><div> elements.playlistCount.textContent = `${playerState.playlist.length} 首歌曲`;</div><div> </div><div> playerState.playlist.forEach((song, index) => {</div><div> const item = document.createElement('div');</div><div> item.className = 'meting-playlist-item';</div><div> if (index === playerState.currentIndex) {</div><div> item.classList.add('playing');</div><div> }//本播放器来自blog.3e0.cn 使用请保留本标识</div><div> </div><div> let durationText = '';</div><div> if (song.duration) {</div><div> const minutes = Math.floor(song.duration / 60);</div><div> const seconds = Math.floor(song.duration % 60);</div><div> durationText = `${minutes}:${seconds.toString().padStart(2, '0')}`;</div><div> }</div><div> </div><div> item.innerHTML = `</div><div> <div class="meting-playlist-item-index">${index + 1}</div></div><div> <div class="meting-playlist-item-info"></div><div> <div class="meting-playlist-item-title">${decodeHtmlEntities(song.name)}</div></div><div> <div class="meting-playlist-item-artist">${decodeHtmlEntities(song.artist)}</div></div><div> </div></div><div> <div class="meting-playlist-item-duration">${durationText}</div></div><div> `;</div><div> </div><div> item.addEventListener('click', () => {</div><div> playerState.isPlaying = true;</div><div> playerState.autoPlayBlocked = false;</div><div> updatePlayButtonState();</div><div> updateLyricsVisibility();</div><div> </div><div> loadSong(index);</div><div> audio.play().catch(e => {</div><div> console.log('播放被阻止:', e);</div><div> playerState.autoPlayBlocked = true;</div><div> playerState.isPlaying = false;</div><div> updatePlayButtonState();</div><div> updateLyricsVisibility();</div><div> savePlayerState();</div><div> });</div><div> });</div><div> </div><div> elements.playlistItems.appendChild(item);</div><div> });</div><div> </div><div> if (elements.playlistContainer.classList.contains('show')) {</div><div> scrollToCurrentSong();</div><div> }</div><div> }</div><div><br></div><div> // 滚动到当前播放的歌曲</div><div> function scrollToCurrentSong() {</div><div> const currentItem = elements.playlistItems.children[playerState.currentIndex];</div><div> if (currentItem) {</div><div> currentItem.scrollIntoView({ behavior: 'smooth', block: 'nearest' });</div><div> }</div><div> }</div><div><br></div><div> // 加载歌曲</div><div> function loadSong(index, position = 0) {</div><div> if (index < 0 || index >= playerState.playlist.length) return;</div><div> </div><div> const song = playerState.playlist[index];</div><div> playerState.currentIndex = index;</div><div> </div><div> // 解码歌曲名和歌手名中的HTML实体</div><div> elements.songTitle.textContent = decodeHtmlEntities(song.name);</div><div> elements.songArtist.textContent = decodeHtmlEntities(song.artist);</div><div> </div><div> if (song.pic) {</div><div> elements.albumCover.src = song.pic;</div><div> }</div><div> </div><div> audio.src = song.url;</div><div> audio.load();</div><div> </div><div> audio.onloadedmetadata = function() {</div><div> if (position > 0 && position < audio.duration) {</div><div> audio.currentTime = position;</div><div> playerState.currentTime = position;</div><div> }</div><div> </div><div> updatePlayButtonState();</div><div> if (playerState.isPlaying) {</div><div> audio.play().catch(e => {</div><div> console.log('播放被阻止:', e);</div><div> playerState.autoPlayBlocked = true;</div><div> playerState.isPlaying = false;</div><div> updatePlayButtonState();</div><div> updateLyricsVisibility();</div><div> savePlayerState();</div><div> });</div><div> }</div><div> };</div><div> </div><div> fetchLyrics(song.lrc);</div><div> </div><div> const items = document.querySelectorAll('.meting-playlist-item');</div><div> items.forEach((item, i) => {</div><div> if (i === index) {</div><div> item.classList.add('playing');</div><div> } else {</div><div> item.classList.remove('playing');</div><div> }</div><div> });</div><div> </div><div> scrollToCurrentSong();</div><div> savePlayerState();</div><div> updateLyricsVisibility();</div><div> updateLyricsButton();</div><div> }</div><div><br></div><div> // 获取歌词</div><div> async function fetchLyrics(url) {</div><div> try {</div><div> const response = await fetch(url);</div><div> const text = await response.text();</div><div> parseLyrics(text);</div><div> } catch (error) {</div><div> console.error('获取歌词失败:', error);</div><div> elements.lyricsLine.textContent = '歌词加载失败';</div><div> adjustPlayerPosition();</div><div> }</div><div> }</div><div><br></div><div> // 解析歌词 - 应用HTML实体解码</div><div> function parseLyrics(text) {</div><div> const lines = text.split('n');</div><div> const lyrics = [];</div><div> const regex = /[(d+):(d+).(d+)](.*)/;</div><div> </div><div> lines.forEach(line => {</div><div> const match = line.match(regex);</div><div> if (match) {</div><div> const minutes = parseInt(match[1]);</div><div> const seconds = parseInt(match[2]);</div><div> const milliseconds = parseInt(match[3]);</div><div> // 解码歌词文本中的HTML实体</div><div> const text = decodeHtmlEntities(match[4].trim());</div><div> const time = minutes * 60 + seconds + milliseconds / 100;</div><div> lyrics.push({ time, text });</div><div> }</div><div> });</div><div> </div><div> lyrics.sort((a, b) => a.time - b.time);</div><div> playerState.lyrics = lyrics;</div><div> elements.lyricsLine.textContent = lyrics.length > 0 ? lyrics[0].text : '暂无歌词';</div><div> adjustPlayerPosition();</div><div> }</div><div><br></div><div> // 更新歌词显示</div><div> function updateLyrics() {</div><div> if (!playerState.lyrics || !playerState.lyricsVisible || playerState.autoPlayBlocked || !playerState.isPlaying) return;</div><div> </div><div> const currentTime = audio.currentTime;</div><div> let currentLine = '';</div><div> </div><div> for (let i = playerState.lyrics.length - 1; i >= 0; i--) {</div><div> if (currentTime >= playerState.lyrics[i].time) {</div><div> currentLine = playerState.lyrics[i].text;</div><div> break;</div><div> }</div><div> }</div><div> </div><div> elements.lyricsLine.textContent = currentLine || '♪';</div><div> adjustPlayerPosition();</div><div> }</div><div><br></div><div> // 同步播放按钮状态</div><div> function updatePlayButtonState() {</div><div> if (playerState.isPlaying) {</div><div> elements.playPauseBtn.innerHTML = '<i class="fa fa-pause"></i>';</div><div> elements.playerMain.classList.add('playing');</div><div> } else {</div><div> elements.playPauseBtn.innerHTML = '<i class="fa fa-play"></i>';</div><div> elements.playerMain.classList.remove('playing');</div><div> }</div><div> }</div><div><br></div><div> // 修复播放后歌词不显示:播放/暂停时立即同步状态</div><div> function togglePlay() {</div><div> if (audio.paused) {</div><div> audio.play().then(() => {</div><div> playerState.isPlaying = true;</div><div> playerState.autoPlayBlocked = false;</div><div> }).catch(e => {</div><div> console.log('播放被阻止:', e);</div><div> playerState.autoPlayBlocked = true;</div><div> playerState.isPlaying = false;</div><div> });</div><div> } else {</div><div> audio.pause();</div><div> playerState.isPlaying = false;</div><div> }</div><div> // 立即更新状态,不等待异步结果</div><div> setTimeout(() => {</div><div> updatePlayButtonState();</div><div> updateLyricsVisibility();</div><div> savePlayerState();</div><div> }, 0);</div><div> }</div><div><br></div><div> // 切换歌词显示/隐藏</div><div> function toggleLyrics() {</div><div> playerState.lyricsVisible = !playerState.lyricsVisible;</div><div> updateLyricsVisibility();</div><div> updateLyricsButton();</div><div> savePlayerState();</div><div> }</div><div><br></div><div> // 下一首</div><div> function nextSong() {</div><div> let nextIndex;</div><div> switch(playerState.mode) {</div><div> case 'random':</div><div> nextIndex = Math.floor(Math.random() * playerState.playlist.length);</div><div> break;</div><div> case 'sequential':</div><div> nextIndex = (playerState.currentIndex + 1) % playerState.playlist.length;</div><div> break;</div><div> case 'loop':</div><div> nextIndex = playerState.currentIndex;</div><div> break;</div><div> }</div><div> </div><div> playerState.isPlaying = true;</div><div> playerState.autoPlayBlocked = false;</div><div> updatePlayButtonState();</div><div> updateLyricsVisibility();</div><div> </div><div> loadSong(nextIndex);</div><div> audio.play().catch(e => {</div><div> console.log('播放被阻止:', e);</div><div> playerState.autoPlayBlocked = true;</div><div> playerState.isPlaying = false;</div><div> updatePlayButtonState();</div><div> updateLyricsVisibility();</div><div> savePlayerState();</div><div> });</div><div> }</div><div><br></div><div> // 上一首</div><div> function prevSong() {</div><div> let prevIndex;</div><div> switch(playerState.mode) {</div><div> case 'random':</div><div> prevIndex = Math.floor(Math.random() * playerState.playlist.length);</div><div> break;</div><div> case 'sequential':</div><div> prevIndex = (playerState.currentIndex - 1 + playerState.playlist.length) % playerState.playlist.length;</div><div> break;</div><div> case 'loop':</div><div> prevIndex = playerState.currentIndex;</div><div> break;</div><div> }</div><div> </div><div> playerState.isPlaying = true;</div><div> playerState.autoPlayBlocked = false;</div><div> updatePlayButtonState();</div><div> updateLyricsVisibility();</div><div> </div><div> loadSong(prevIndex);</div><div> audio.play().catch(e => {</div><div> console.log('播放被阻止:', e);</div><div> playerState.autoPlayBlocked = true;</div><div> playerState.isPlaying = false;</div><div> updatePlayButtonState();</div><div> updateLyricsVisibility();</div><div> savePlayerState();</div><div> });</div><div> }</div><div><br></div><div> // 切换播放模式</div><div> function toggleMode() {</div><div> const modes = ['random', 'sequential', 'loop'];</div><div> const currentIndex = modes.indexOf(playerState.mode);</div><div> playerState.mode = modes[(currentIndex + 1) % modes.length];</div><div> updateModeButton();</div><div> savePlayerState();</div><div> }</div><div><br></div><div> // 切换播放器最小化状态</div><div> function toggleMinimize() {</div><div> playerState.minimized = !playerState.minimized;</div><div> </div><div> if (playerState.minimized) {</div><div> elements.playerContainer.classList.add('minimized');</div><div> elements.togglePlayerBtn.innerHTML = '<i class="fa fa-plus"></i>';</div><div> elements.playlistContainer.classList.remove('show');</div><div> } else {</div><div> elements.playerContainer.classList.remove('minimized');</div><div> elements.togglePlayerBtn.innerHTML = '<i class="fa fa-minus"></i>';</div><div> }</div><div> </div><div> savePlayerState();</div><div> }</div><div><br></div><div> // 切换播放列表显示</div><div> function togglePlaylist() {</div><div> if (playerState.minimized) {</div><div> toggleMinimize();</div><div> }</div><div> elements.playlistContainer.classList.toggle('show');</div><div> </div><div> if (elements.playlistContainer.classList.contains('show')) {</div><div> setTimeout(() => {</div><div> scrollToCurrentSong();</div><div> }, 0);</div><div> }</div><div> }</div><div><br></div><div> // 更新进度条</div><div> function updateProgress() {</div><div> const percent = (audio.currentTime / audio.duration) * 100;</div><div> elements.progressBar.style.width = `${percent}%`;</div><div> </div><div> const currentMinutes = Math.floor(audio.currentTime / 60);</div><div> const currentSeconds = Math.floor(audio.currentTime % 60);</div><div> const durationMinutes = Math.floor(audio.duration / 60);</div><div> const durationSeconds = Math.floor(audio.duration % 60);</div><div> </div><div> elements.timeDisplay.textContent = </div><div> `${currentMinutes.toString().padStart(2, '0')}:${currentSeconds.toString().padStart(2, '0')} / ${durationMinutes.toString().padStart(2, '0')}:${durationSeconds.toString().padStart(2, '0')}`;</div><div> </div><div> updateLyrics();</div><div> </div><div> if (Math.floor(audio.currentTime) % 5 === 0) {</div><div> savePlayerState();</div><div> }</div><div> }</div><div><br></div><div> // 设置进度</div><div> function setProgress(e) {</div><div> const width = elements.progressContainer.clientWidth;</div><div> const clickX = e.offsetX;</div><div> const duration = audio.duration;</div><div> </div><div> audio.currentTime = (clickX / width) * duration;</div><div> savePlayerState();</div><div> }</div><div><br></div><div> // 设置音量</div><div> function setVolume() {</div><div> const volume = elements.volumeSlider.value;</div><div> audio.volume = volume / 100;</div><div> playerState.volume = volume;</div><div> </div><div> updateVolumeIcon();</div><div> savePlayerState();</div><div> }</div><div><br></div><div> // 初始化事件监听</div><div> function initEventListeners() {</div><div> elements.playPauseBtn.addEventListener('click', togglePlay);</div><div> elements.prevBtn.addEventListener('click', prevSong);</div><div> elements.nextBtn.addEventListener('click', nextSong);</div><div> elements.progressContainer.addEventListener('click', setProgress);</div><div> elements.volumeSlider.addEventListener('input', setVolume);</div><div> elements.togglePlayerBtn.addEventListener('click', toggleMinimize);</div><div> elements.lyricsBtn.addEventListener('click', toggleLyrics);</div><div> elements.playlistBtn.addEventListener('click', togglePlaylist);</div><div> elements.modeBtn.addEventListener('click', toggleMode);</div><div> </div><div> audio.addEventListener('timeupdate', updateProgress);</div><div> audio.addEventListener('ended', nextSong);</div><div> </div><div> // 监听音频原生事件,确保状态100%同步</div><div> audio.addEventListener('play', () => {</div><div> playerState.isPlaying = true;</div><div> updatePlayButtonState();</div><div> updateLyricsVisibility();</div><div> });</div><div> audio.addEventListener('pause', () => {</div><div> playerState.isPlaying = false;</div><div> updatePlayButtonState();</div><div> updateLyricsVisibility();</div><div> });</div><div> </div><div> window.addEventListener('beforeunload', savePlayerState);</div><div> document.addEventListener('visibilitychange', function() {</div><div> if (document.hidden) {</div><div> savePlayerState();</div><div> }</div><div> });</div><div> </div><div> window.addEventListener('resize', function() {</div><div> updateLyricsVisibility();</div><div> });</div><div><br></div><div> const lyricsObserver = new MutationObserver(() => {</div><div> adjustPlayerPosition();</div><div> });</div><div> lyricsObserver.observe(elements.lyricsLine, { childList: true, characterData: true, subtree: true });</div><div> }</div><div><br></div><div> // 初始化播放器</div><div> function initPlayer() {</div><div> loadPlayerState();</div><div> initEventListeners();</div><div> fetchPlaylist();</div><div> updatePlayButtonState();</div><div> updateLyricsButton();</div><div> }</div><div><br></div><div> // 启动播放器</div><div> initPlayer();</div><div>})();</div><div></script>
1111