Add global mini-player and improve configuration

- Add global activeRoom store for persistent WebSocket connection
- Add MiniPlayer component for playback controls across pages
- Add chunked S3 streaming with 64KB chunks and Range support
- Add queue item removal button
- Move DB credentials to environment variables
- Update .env.example with DB configuration

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-12 15:02:02 +03:00
parent 2f1e1f35e3
commit f77a453158
12 changed files with 572 additions and 119 deletions

View File

@@ -2,28 +2,22 @@
<div class="room-page" v-if="room">
<div class="room-header">
<h1>{{ room.name }}</h1>
<button class="btn-secondary" @click="leaveAndGoHome">Выйти из комнаты</button>
</div>
<div class="room-layout">
<div class="main-section">
<AudioPlayer
:ws="websocket"
@player-action="handlePlayerAction"
/>
<div class="queue-section card">
<div class="queue-header">
<h3>Очередь</h3>
<button class="btn-secondary" @click="showAddTrack = true">Добавить</button>
</div>
<Queue :queue="roomStore.queue" @play-track="playTrack" />
<Queue :queue="roomStore.queue" @play-track="playTrack" @remove-track="removeFromQueue" />
</div>
</div>
<div class="side-section">
<ParticipantsList :participants="roomStore.participants" />
<ChatWindow :room-id="roomId" :ws="websocket" />
<ChatWindow :room-id="roomId" />
</div>
</div>
@@ -39,14 +33,11 @@
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { useRoomStore } from '../stores/room'
import { useTracksStore } from '../stores/tracks'
import { usePlayerStore } from '../stores/player'
import { useWebSocket } from '../composables/useWebSocket'
import { usePlayer } from '../composables/usePlayer'
import AudioPlayer from '../components/player/AudioPlayer.vue'
import { useActiveRoomStore } from '../stores/activeRoom'
import Queue from '../components/room/Queue.vue'
import ParticipantsList from '../components/room/ParticipantsList.vue'
import ChatWindow from '../components/chat/ChatWindow.vue'
@@ -54,45 +45,14 @@ import TrackList from '../components/tracks/TrackList.vue'
import Modal from '../components/common/Modal.vue'
const route = useRoute()
const router = useRouter()
const roomStore = useRoomStore()
const tracksStore = useTracksStore()
const playerStore = usePlayerStore()
const activeRoomStore = useActiveRoomStore()
const roomId = route.params.id
const room = ref(null)
const showAddTrack = ref(false)
const { syncToState, setOnTrackEnded } = usePlayer()
function handleTrackEnded() {
sendPlayerAction('next')
}
function handleWsMessage(msg) {
switch (msg.type) {
case 'player_state':
case 'sync_state':
// Call syncToState BEFORE updating store so it can detect URL changes
syncToState(msg)
playerStore.setPlayerState(msg)
break
case 'user_joined':
roomStore.addParticipant(msg.user)
break
case 'user_left':
roomStore.removeParticipant(msg.user_id)
break
case 'queue_updated':
roomStore.fetchQueue(roomId)
break
}
}
const { connect, disconnect, sendPlayerAction, connected } = useWebSocket(roomId, handleWsMessage)
const websocket = { sendPlayerAction, connected }
onMounted(async () => {
await roomStore.fetchRoom(roomId)
room.value = roomStore.currentRoom
@@ -101,22 +61,12 @@ onMounted(async () => {
await roomStore.fetchQueue(roomId)
await tracksStore.fetchTracks()
// Set callback for when track ends
setOnTrackEnded(handleTrackEnded)
connect()
// Connect to room via global store
activeRoomStore.connect(roomId, room.value.name)
})
onUnmounted(() => {
disconnect()
})
function handlePlayerAction(action, position) {
sendPlayerAction(action, position)
}
function playTrack(track) {
sendPlayerAction('set_track', null, track.id)
activeRoomStore.sendPlayerAction('set_track', null, track.id)
}
async function addTrackToQueue(track) {
@@ -124,9 +74,8 @@ async function addTrackToQueue(track) {
showAddTrack.value = false
}
async function leaveAndGoHome() {
await roomStore.leaveRoom(roomId)
router.push('/')
async function removeFromQueue(track) {
await roomStore.removeFromQueue(roomId, track.id)
}
</script>