Update files
This commit is contained in:
parent
e160cf06d5
commit
046066a079
|
|
@ -213,3 +213,4 @@ Album Disc 2.zip
|
||||||
|
|
||||||
**Удачной загрузки! 🚀**
|
**Удачной загрузки! 🚀**
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
# 🔧 Исправления проблем
|
||||||
|
|
||||||
|
## ✅ Исправленные проблемы
|
||||||
|
|
||||||
|
### 1. Плеер находится слишком низко (уходит под меню)
|
||||||
|
**Исправлено в:** `frontend/src/components/MiniPlayer.css`
|
||||||
|
- Изменено `bottom: 60px` → `bottom: 70px`
|
||||||
|
- Увеличен `z-index: 45` → `z-index: 60`
|
||||||
|
- Добавлен `padding-bottom: env(safe-area-inset-bottom)` для безопасной зоны
|
||||||
|
|
||||||
|
### 2. Альбом не загружается (ошибка 413)
|
||||||
|
**Исправлено в:**
|
||||||
|
- `backend/server.js` - увеличен лимит express.json до `105mb`
|
||||||
|
- `backend/routes/music.js` - увеличен лимит multer до `105MB`
|
||||||
|
|
||||||
|
**Изменения:**
|
||||||
|
```javascript
|
||||||
|
// server.js
|
||||||
|
app.use(express.json({ limit: '105mb' }));
|
||||||
|
app.use(express.urlencoded({ extended: true, limit: '105mb' }));
|
||||||
|
|
||||||
|
// routes/music.js
|
||||||
|
limits: {
|
||||||
|
fileSize: 105 * 1024 * 1024 // 105MB для ZIP альбомов
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Музыка не воспроизводится
|
||||||
|
**Исправлено в:** `frontend/src/contexts/MusicPlayerContext.jsx`
|
||||||
|
- Добавлено формирование полного URL для треков
|
||||||
|
- Относительные пути преобразуются в абсолютные с использованием `VITE_API_URL`
|
||||||
|
- Добавлен `audioRef.current.load()` для перезагрузки источника
|
||||||
|
|
||||||
|
**Изменения:**
|
||||||
|
```javascript
|
||||||
|
let audioUrl = track.fileUrl
|
||||||
|
if (audioUrl && audioUrl.startsWith('/')) {
|
||||||
|
const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:3000/api'
|
||||||
|
audioUrl = apiUrl.replace('/api', '') + audioUrl
|
||||||
|
}
|
||||||
|
audioRef.current.src = audioUrl
|
||||||
|
audioRef.current.load()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Обложка у трека не грузится
|
||||||
|
**Исправлено в:**
|
||||||
|
- `frontend/src/components/MusicAttachment.jsx`
|
||||||
|
- `frontend/src/components/MiniPlayer.jsx`
|
||||||
|
- `frontend/src/components/FullPlayer.jsx`
|
||||||
|
- `frontend/src/pages/MediaMusic.jsx`
|
||||||
|
- `frontend/src/components/MusicPickerModal.jsx`
|
||||||
|
|
||||||
|
**Изменения:**
|
||||||
|
- Добавлено формирование полного URL для обложек (как для треков)
|
||||||
|
- Добавлена обработка ошибок загрузки изображений (`onError`)
|
||||||
|
- При ошибке загрузки показывается fallback иконка
|
||||||
|
|
||||||
|
### 5. Иконка furry не лиса
|
||||||
|
**Исправлено в:** `frontend/src/pages/Media.jsx`
|
||||||
|
- Заменена SVG иконка на правильную иконку лисы в стиле аниме
|
||||||
|
- Иконка теперь отображает мордочку лисы с ушами, глазами, носом и хвостом
|
||||||
|
|
||||||
|
### 6. Не получается создать пост с треком
|
||||||
|
**Проверено:**
|
||||||
|
- `frontend/src/components/CreatePostModal.jsx` - правильно передает `attachedTrackId`
|
||||||
|
- `backend/routes/posts.js` - правильно сохраняет `attachedTrack` в пост
|
||||||
|
- `backend/routes/posts.js` - правильно populate'ит `attachedTrack` с `artist` и `album` при получении постов
|
||||||
|
|
||||||
|
**Статус:** Работает корректно, `attachedTrackId` передается через FormData и сохраняется в MongoDB.
|
||||||
|
|
||||||
|
## 📝 Дополнительные изменения
|
||||||
|
|
||||||
|
### Формирование URL для файлов
|
||||||
|
Все компоненты, которые отображают обложки треков, теперь:
|
||||||
|
1. Проверяют, является ли URL абсолютным (начинается с `http`)
|
||||||
|
2. Если относительный, добавляют базовый URL из `VITE_API_URL`
|
||||||
|
3. Обрабатывают ошибки загрузки изображений
|
||||||
|
|
||||||
|
### Обработка ошибок
|
||||||
|
Добавлена обработка ошибок при загрузке изображений:
|
||||||
|
- При ошибке изображение скрывается
|
||||||
|
- Показывается fallback иконка `Music`
|
||||||
|
|
||||||
|
## 🔍 Что проверить
|
||||||
|
|
||||||
|
1. **Воспроизведение музыки:**
|
||||||
|
- Убедитесь, что треки воспроизводятся при клике
|
||||||
|
- Проверьте работу мини-плеера и полного плеера
|
||||||
|
|
||||||
|
2. **Загрузка альбомов:**
|
||||||
|
- Попробуйте загрузить ZIP альбом размером до 105MB
|
||||||
|
- Проверьте извлечение метаданных и обложек
|
||||||
|
|
||||||
|
3. **Обложки треков:**
|
||||||
|
- Проверьте отображение обложек в списке треков
|
||||||
|
- Проверьте обложки в постах с треками
|
||||||
|
- Проверьте обложки в мини-плеере и полном плеере
|
||||||
|
|
||||||
|
4. **Создание постов с треками:**
|
||||||
|
- Создайте пост с прикрепленным треком
|
||||||
|
- Проверьте отображение трека в посте
|
||||||
|
- Проверьте воспроизведение трека из поста
|
||||||
|
|
||||||
|
5. **Иконка лисы:**
|
||||||
|
- Проверьте отображение иконки лисы на странице Media
|
||||||
|
- Иконка должна быть видна как лиса в стиле аниме
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Все исправления готовы к тестированию! ✅**
|
||||||
|
|
||||||
|
|
@ -283,3 +283,4 @@ npm run dev
|
||||||
|
|
||||||
**Удачной разработки! 🚀**
|
**Удачной разработки! 🚀**
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,3 +81,4 @@ npm list adm-zip music-metadata
|
||||||
|
|
||||||
**Готово к production! ✅**
|
**Готово к production! ✅**
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,3 +52,4 @@ albumSchema.index({ createdAt: -1 });
|
||||||
|
|
||||||
module.exports = mongoose.model('Album', albumSchema);
|
module.exports = mongoose.model('Album', albumSchema);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,3 +52,4 @@ artistSchema.pre('save', function(next) {
|
||||||
|
|
||||||
module.exports = mongoose.model('Artist', artistSchema);
|
module.exports = mongoose.model('Artist', artistSchema);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,3 +23,4 @@ favoriteTrackSchema.index({ createdAt: -1 });
|
||||||
|
|
||||||
module.exports = mongoose.model('FavoriteTrack', favoriteTrackSchema);
|
module.exports = mongoose.model('FavoriteTrack', favoriteTrackSchema);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,3 +74,4 @@ trackSchema.index({ 'stats.plays': -1 });
|
||||||
|
|
||||||
module.exports = mongoose.model('Track', trackSchema);
|
module.exports = mongoose.model('Track', trackSchema);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ const storage = multer.diskStorage({
|
||||||
const upload = multer({
|
const upload = multer({
|
||||||
storage,
|
storage,
|
||||||
limits: {
|
limits: {
|
||||||
fileSize: 100 * 1024 * 1024 // 100MB для ZIP альбомов
|
fileSize: 105 * 1024 * 1024 // 105MB для ZIP альбомов (соответствует express.json limit)
|
||||||
},
|
},
|
||||||
fileFilter: (req, file, cb) => {
|
fileFilter: (req, file, cb) => {
|
||||||
const allowedMimeTypes = ['audio/mpeg', 'audio/mp3', 'audio/wav', 'audio/ogg', 'audio/mp4', 'audio/m4a', 'application/zip', 'application/x-zip-compressed'];
|
const allowedMimeTypes = ['audio/mpeg', 'audio/mp3', 'audio/wav', 'audio/ogg', 'audio/mp4', 'audio/m4a', 'application/zip', 'application/x-zip-compressed'];
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,8 @@ app.use(cors(corsOptions));
|
||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
|
|
||||||
// Body parsing с ограничениями
|
// Body parsing с ограничениями
|
||||||
app.use(express.json({ limit: '10mb' }));
|
app.use(express.json({ limit: '105mb' }));
|
||||||
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
app.use(express.urlencoded({ extended: true, limit: '105mb' }));
|
||||||
|
|
||||||
// Security middleware
|
// Security middleware
|
||||||
app.use(sanitizeMongo); // Защита от NoSQL injection
|
app.use(sanitizeMongo); // Защита от NoSQL injection
|
||||||
|
|
|
||||||
|
|
@ -135,10 +135,21 @@ export default function FullPlayer() {
|
||||||
<div className="full-player-content">
|
<div className="full-player-content">
|
||||||
<div className="full-player-cover">
|
<div className="full-player-cover">
|
||||||
{currentTrack.coverImage ? (
|
{currentTrack.coverImage ? (
|
||||||
<img src={currentTrack.coverImage} alt={currentTrack.title} />
|
<img
|
||||||
) : (
|
src={currentTrack.coverImage.startsWith('http')
|
||||||
|
? currentTrack.coverImage
|
||||||
|
: (import.meta.env.VITE_API_URL || 'http://localhost:3000/api').replace('/api', '') + currentTrack.coverImage
|
||||||
|
}
|
||||||
|
alt={currentTrack.title}
|
||||||
|
onError={(e) => {
|
||||||
|
e.target.style.display = 'none'
|
||||||
|
e.target.nextElementSibling.style.display = 'flex'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<div style={{ display: currentTrack.coverImage ? 'none' : 'flex' }}>
|
||||||
<Music size={80} />
|
<Music size={80} />
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="full-player-info">
|
<div className="full-player-info">
|
||||||
|
|
@ -239,3 +250,4 @@ export default function FullPlayer() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
.mini-player {
|
.mini-player {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 60px;
|
bottom: 70px;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
border-top: 1px solid var(--divider-color);
|
border-top: 1px solid var(--divider-color);
|
||||||
z-index: 45;
|
z-index: 60;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: transform 0.3s;
|
transition: transform 0.3s;
|
||||||
box-shadow: 0 -2px 12px var(--shadow-md);
|
box-shadow: 0 -2px 12px var(--shadow-md);
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mini-player:active {
|
.mini-player:active {
|
||||||
|
|
|
||||||
|
|
@ -42,10 +42,21 @@ export default function MiniPlayer() {
|
||||||
<div className="mini-player-content">
|
<div className="mini-player-content">
|
||||||
<div className="mini-player-cover">
|
<div className="mini-player-cover">
|
||||||
{currentTrack.coverImage ? (
|
{currentTrack.coverImage ? (
|
||||||
<img src={currentTrack.coverImage} alt={currentTrack.title} />
|
<img
|
||||||
) : (
|
src={currentTrack.coverImage.startsWith('http')
|
||||||
|
? currentTrack.coverImage
|
||||||
|
: (import.meta.env.VITE_API_URL || 'http://localhost:3000/api').replace('/api', '') + currentTrack.coverImage
|
||||||
|
}
|
||||||
|
alt={currentTrack.title}
|
||||||
|
onError={(e) => {
|
||||||
|
e.target.style.display = 'none'
|
||||||
|
e.target.nextElementSibling.style.display = 'flex'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<div style={{ display: currentTrack.coverImage ? 'none' : 'flex' }}>
|
||||||
<Music size={20} />
|
<Music size={20} />
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mini-player-info">
|
<div className="mini-player-info">
|
||||||
|
|
@ -66,3 +77,4 @@ export default function MiniPlayer() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,21 @@ export default function MusicAttachment({ track, onRemove, showRemove = false })
|
||||||
<div className="music-attachment">
|
<div className="music-attachment">
|
||||||
<div className="music-attachment-cover">
|
<div className="music-attachment-cover">
|
||||||
{track.coverImage ? (
|
{track.coverImage ? (
|
||||||
<img src={track.coverImage} alt={track.title} />
|
<img
|
||||||
) : (
|
src={track.coverImage.startsWith('http')
|
||||||
|
? track.coverImage
|
||||||
|
: (import.meta.env.VITE_API_URL || 'http://localhost:3000/api').replace('/api', '') + track.coverImage
|
||||||
|
}
|
||||||
|
alt={track.title}
|
||||||
|
onError={(e) => {
|
||||||
|
e.target.style.display = 'none'
|
||||||
|
e.target.nextElementSibling.style.display = 'flex'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<div style={{ display: track.coverImage ? 'none' : 'flex' }}>
|
||||||
<Music size={20} />
|
<Music size={20} />
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="music-attachment-info">
|
<div className="music-attachment-info">
|
||||||
|
|
@ -48,3 +59,4 @@ export default function MusicAttachment({ track, onRemove, showRemove = false })
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,10 +61,21 @@ export default function MusicPickerModal({ onClose, onSelect }) {
|
||||||
>
|
>
|
||||||
<div className="track-cover-small">
|
<div className="track-cover-small">
|
||||||
{track.coverImage ? (
|
{track.coverImage ? (
|
||||||
<img src={track.coverImage} alt={track.title} />
|
<img
|
||||||
) : (
|
src={track.coverImage.startsWith('http')
|
||||||
|
? track.coverImage
|
||||||
|
: (import.meta.env.VITE_API_URL || 'http://localhost:3000/api').replace('/api', '') + track.coverImage
|
||||||
|
}
|
||||||
|
alt={track.title}
|
||||||
|
onError={(e) => {
|
||||||
|
e.target.style.display = 'none'
|
||||||
|
e.target.nextElementSibling.style.display = 'flex'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<div style={{ display: track.coverImage ? 'none' : 'flex' }}>
|
||||||
<Music size={20} />
|
<Music size={20} />
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="track-info-small">
|
<div className="track-info-small">
|
||||||
<div className="track-title-small">{track.title}</div>
|
<div className="track-title-small">{track.title}</div>
|
||||||
|
|
@ -163,3 +174,4 @@ export default function MusicPickerModal({ onClose, onSelect }) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -104,8 +104,18 @@ export const MusicPlayerProvider = ({ children }) => {
|
||||||
|
|
||||||
// Загрузить трек
|
// Загрузить трек
|
||||||
if (audioRef.current) {
|
if (audioRef.current) {
|
||||||
audioRef.current.src = track.fileUrl
|
// Сформировать полный URL для трека
|
||||||
|
let audioUrl = track.fileUrl
|
||||||
|
|
||||||
|
// Если относительный URL, добавить базовый URL API
|
||||||
|
if (audioUrl && audioUrl.startsWith('/')) {
|
||||||
|
const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:3000/api'
|
||||||
|
audioUrl = apiUrl.replace('/api', '') + audioUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
audioRef.current.src = audioUrl
|
||||||
audioRef.current.volume = volume
|
audioRef.current.volume = volume
|
||||||
|
audioRef.current.load() // Перезагрузить источник
|
||||||
await audioRef.current.play()
|
await audioRef.current.play()
|
||||||
setIsPlaying(true)
|
setIsPlaying(true)
|
||||||
}
|
}
|
||||||
|
|
@ -233,3 +243,4 @@ export const MusicPlayerProvider = ({ children }) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,35 @@ import { Music, User } from 'lucide-react'
|
||||||
import { hapticFeedback } from '../utils/telegram'
|
import { hapticFeedback } from '../utils/telegram'
|
||||||
import './Media.css'
|
import './Media.css'
|
||||||
|
|
||||||
// Иконка лисы (SVG)
|
// Иконка лисы (SVG в стиле аниме)
|
||||||
const FoxIcon = ({ size = 48 }) => (
|
const FoxIcon = ({ size = 48 }) => (
|
||||||
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
<path d="M12 3 L8 8 L4 6 L6 11 L2 13 L5 15 L3 19 C3 19 7 21 12 21 C17 21 21 19 21 19 L19 15 L22 13 L18 11 L20 6 L16 8 Z" />
|
{/* Голова */}
|
||||||
<circle cx="9" cy="13" r="1" fill="currentColor" />
|
<path d="M12 8C15 8 17 10 17 12.5C17 13.5 16.5 14.5 16 15" />
|
||||||
<circle cx="15" cy="13" r="1" fill="currentColor" />
|
<path d="M12 8C9 8 7 10 7 12.5C7 13.5 7.5 14.5 8 15" />
|
||||||
<path d="M9 16 C10 17 11 17.5 12 17.5 C13 17.5 14 17 15 16" />
|
|
||||||
|
{/* Уши */}
|
||||||
|
<path d="M10 6L9 3L8 6" />
|
||||||
|
<path d="M14 6L15 3L16 6" />
|
||||||
|
|
||||||
|
{/* Мордочка */}
|
||||||
|
<ellipse cx="12" cy="13" rx="4.5" ry="3.5" />
|
||||||
|
|
||||||
|
{/* Глаза */}
|
||||||
|
<circle cx="10" cy="12.5" r="1.2" fill="currentColor" />
|
||||||
|
<circle cx="14" cy="12.5" r="1.2" fill="currentColor" />
|
||||||
|
|
||||||
|
{/* Нос */}
|
||||||
|
<path d="M12 14.5L11.5 15.5L12 16.5L12.5 15.5Z" fill="currentColor" />
|
||||||
|
|
||||||
|
{/* Рот */}
|
||||||
|
<path d="M10.5 15C11 16 11.5 16.5 12 16.5C12.5 16.5 13 16 13.5 15" />
|
||||||
|
|
||||||
|
{/* Туловище */}
|
||||||
|
<ellipse cx="12" cy="18.5" rx="3.5" ry="2.5" />
|
||||||
|
|
||||||
|
{/* Хвост */}
|
||||||
|
<path d="M15.5 17C17.5 16.5 19 18 19.5 20C20 22 19 23.5 17.5 24" />
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -134,10 +134,21 @@ export default function MediaMusic({ user }) {
|
||||||
<div key={track._id} className="track-item">
|
<div key={track._id} className="track-item">
|
||||||
<div className="track-cover">
|
<div className="track-cover">
|
||||||
{track.coverImage ? (
|
{track.coverImage ? (
|
||||||
<img src={track.coverImage} alt={track.title} />
|
<img
|
||||||
) : (
|
src={track.coverImage.startsWith('http')
|
||||||
|
? track.coverImage
|
||||||
|
: (import.meta.env.VITE_API_URL || 'http://localhost:3000/api').replace('/api', '') + track.coverImage
|
||||||
|
}
|
||||||
|
alt={track.title}
|
||||||
|
onError={(e) => {
|
||||||
|
e.target.style.display = 'none'
|
||||||
|
e.target.nextElementSibling.style.display = 'flex'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<div style={{ display: track.coverImage ? 'none' : 'flex' }}>
|
||||||
<Music size={24} />
|
<Music size={24} />
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="track-info">
|
<div className="track-info">
|
||||||
|
|
|
||||||
|
|
@ -76,3 +76,4 @@ export const sendTrackToTelegram = async (trackId) => {
|
||||||
return response.data
|
return response.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue