
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 用户浏览器 │ ←──→ │ Flask 后端 │ ←──→ │ 又拍云存储 │
│ (前端 UI) │ │ (API 服务) │ │ (音乐文件) │
└─────────────┘ └─────────────┘ └─────────────┘默认情况下,浏览器访问音频 URL 会触发下载,因为:
响应头 Content-Type 错误(如 application/octet-stream)
缺少 Content-Disposition: inline 头
上传时设置正确的 HTTP 头:
cheaders = {
'Content-Type': 'audio/mpeg', # 或 audio/ogg、audio/wav 等
'Content-Disposition': 'inline' # 关键:浏览器内嵌播放
}
up.put(remote_path, file_content, headers=headers)from flask import Flask, request, jsonify, render_template
from flask_cors import CORS
import upyun
app = Flask(__name__)
CORS(app)
# ========== 又拍云配置 ==========
UPYUN_SERVICE = '你的服务名'
UPYUN_OPERATOR = '你的操作员'
UPYUN_PASSWORD = '你的密码'
UPYUN_DOMAIN = 'http://你的域名'
up = upyun.UpYun(UPYUN_SERVICE, UPYUN_OPERATOR, UPYUN_PASSWORD)
# ========== 路由 ==========
@app.route('/')
def index():
"""用户前台"""
return render_template('index.html')
@app.route('/admin')
def admin():
"""管理后台"""
return render_template('admin.html')
@app.route('/api/upload', methods=['POST'])
def upload():
"""上传音乐(带在线播放头)"""
file = request.files['file']
# 验证文件类型
allowed = ('.mp3', '.m4a', '.ogg', '.wav', '.flac')
if not file.filename.lower().endswith(allowed):
return jsonify({'error': '不支持的格式'}), 400
# 设置在线播放头
headers = {
'Content-Type': get_mime_type(file.filename),
'Content-Disposition': 'inline' # 关键!
}
remote_path = f'/music/{file.filename}'
up.put(remote_path, file.read(), headers=headers)
return jsonify({
'success': True,
'url': f'{UPYUN_DOMAIN}/music/{file.filename}'
})
@app.route('/api/songs')
def get_songs():
"""获取音乐列表"""
items = up.getlist('/music')
songs = []
for item in items:
# 注意:又拍云返回 type='N' 表示文件,'F' 表示文件夹
if item.get('type') == 'N':
filename = item['name']
# 移除扩展名用于显示
display_name = remove_ext(filename)
songs.append({
'name': display_name,
'filename': filename,
'url': f'{UPYUN_DOMAIN}/music/{filename}',
'size': format_size(int(item['size']))
})
return jsonify(songs)
@app.route('/api/search')
def search():
"""搜索音乐"""
keyword = request.args.get('q', '').lower()
items = up.getlist('/music')
songs = []
for item in items:
if item.get('type') == 'N':
filename = item['name']
display_name = remove_ext(filename)
# 模糊匹配
if keyword in filename.lower() or keyword in display_name.lower():
songs.append({
'name': display_name,
'filename': filename,
'url': f'{UPYUN_DOMAIN}/music/{filename}',
'size': format_size(int(item['size']))
})
return jsonify(songs)
@app.route('/api/delete/<path:filename>', methods=['DELETE'])
def delete(filename):
"""删除音乐"""
up.delete(f'/music/{filename}')
return jsonify({'success': True})
# ========== 工具函数 ==========
def get_mime_type(filename):
"""获取 MIME 类型"""
ext = filename.lower().split('.')[-1]
types = {
'mp3': 'audio/mpeg',
'm4a': 'audio/mp4',
'ogg': 'audio/ogg',
'wav': 'audio/wav',
'flac': 'audio/flac'
}
return types.get(ext, 'audio/mpeg')
def remove_ext(filename):
"""移除音频扩展名"""
for ext in ['.mp3', '.m4a', '.ogg', '.wav', '.flac']:
filename = filename.replace(ext, '')
return filename
def format_size(size):
"""格式化文件大小"""
for unit in ['B', 'KB', 'MB', 'GB']:
if size < 1024:
return f"{size:.1f} {unit}"
size /= 1024
if __name__ == '__main__':
app.run(debug=True, port=5000)坑 1:又拍云返回的 type 字段
# 错误 ❌
if item['type'] == 'file':
# 正确 ✅ 又拍云用 'N' 表示文件,'F' 表示文件夹
if item.get('type') == 'N':坑 2:文件大小是字符串
# 错误 ❌
size = item['size'] # 直接传会是字符串
# 正确 ✅
size = int(item['size']) # 需要转整数// 实时搜索(防抖)
let searchTimer;
function handleSearch(keyword) {
clearTimeout(searchTimer);
searchTimer = setTimeout(() => {
const filtered = allSongs.filter(song =>
song.name.toLowerCase().includes(keyword.toLowerCase())
);
renderSongs(filtered);
}, 300);
}
// 横向滚动
function scrollSlider(direction) {
const container = document.getElementById('slider');
container.scrollBy({ left: direction * 600, behavior: 'smooth' });
}
// 播放控制
function playSong(url, name) {
const audio = document.getElementById('audioPlayer');
audio.src = url;
audio.play();
// 更新 UI...
}music-player/
├── app.py # Flask 后端
├── requirements.txt # 依赖
└── templates/
├── index.html # 用户前台(Netflix 风格)
└── admin.html # 管理后台(上传/删除)pip install flask flask-cors upyun创建对象存储服务(如 niguamusic)
创建操作员,赋予读取、写入、删除权限
绑定自定义域名(如 niguamusic.godnome.xyz)
在 app.py 填入配置信息
九、源码获取
待后续上传github