Lili Records v1.11
This commit is contained in:
parent
832958c654
commit
ee5b204fea
Binary file not shown.
18
core/migrations/0002_station_metadata_api_url.py
Normal file
18
core/migrations/0002_station_metadata_api_url.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-01-30 13:50
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='station',
|
||||||
|
name='metadata_api_url',
|
||||||
|
field=models.URLField(blank=True, help_text='Optional API URL to fetch current track metadata', null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
Binary file not shown.
@ -3,6 +3,7 @@ from django.db import models
|
|||||||
class Station(models.Model):
|
class Station(models.Model):
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
stream_url = models.URLField(help_text="URL for the radio stream")
|
stream_url = models.URLField(help_text="URL for the radio stream")
|
||||||
|
metadata_api_url = models.URLField(blank=True, null=True, help_text="Optional API URL to fetch current track metadata")
|
||||||
description = models.TextField(blank=True)
|
description = models.TextField(blank=True)
|
||||||
color = models.CharField(max_length=7, default="#FF007F", help_text="Hex color for the vinyl")
|
color = models.CharField(max_length=7, default="#FF007F", help_text="Hex color for the vinyl")
|
||||||
image = models.FileField(upload_to="stations/", blank=True, null=True)
|
image = models.FileField(upload_to="stations/", blank=True, null=True)
|
||||||
@ -19,4 +20,4 @@ class Track(models.Model):
|
|||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.artist} - {self.title}"
|
return f"{self.artist} - {self.title}"
|
||||||
@ -34,7 +34,7 @@
|
|||||||
<div class="v-bar"></div><div class="v-bar"></div><div class="v-bar"></div><div class="v-bar"></div><div class="v-bar"></div>
|
<div class="v-bar"></div><div class="v-bar"></div><div class="v-bar"></div><div class="v-bar"></div><div class="v-bar"></div>
|
||||||
<div class="v-bar"></div><div class="v-bar"></div><div class="v-bar"></div><div class="v-bar"></div><div class="v-bar"></div>
|
<div class="v-bar"></div><div class="v-bar"></div><div class="v-bar"></div><div class="v-bar"></div><div class="v-bar"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="audio-controls mb-4 p-3 rounded-3 bg-dark bg-opacity-50 border border-secondary">
|
<div class="audio-controls mb-4 p-3 rounded-3 bg-dark bg-opacity-50 border border-secondary">
|
||||||
<audio id="audio-player"></audio>
|
<audio id="audio-player"></audio>
|
||||||
<div class="d-flex align-items-center gap-3">
|
<div class="d-flex align-items-center gap-3">
|
||||||
@ -53,10 +53,11 @@
|
|||||||
<h5 class="mb-3 text-uppercase small ls-wide fw-bold text-secondary">Available Stations</h5>
|
<h5 class="mb-3 text-uppercase small ls-wide fw-bold text-secondary">Available Stations</h5>
|
||||||
<div class="list-group list-group-flush bg-transparent custom-scrollbar" style="max-height: 250px; overflow-y: auto;">
|
<div class="list-group list-group-flush bg-transparent custom-scrollbar" style="max-height: 250px; overflow-y: auto;">
|
||||||
{% for station in stations %}
|
{% for station in stations %}
|
||||||
<button class="list-group-item list-group-item-action bg-transparent text-white border-0 py-3 rounded-3 mb-2 station-item"
|
<button class="list-group-item list-group-item-action bg-transparent text-white border-0 py-3 rounded-3 mb-2 station-item"
|
||||||
data-url="{{ station.stream_url }}"
|
data-url="{{ station.stream_url }}"
|
||||||
data-name="{{ station.name }}"
|
data-name="{{ station.name }}"
|
||||||
data-color="{{ station.color }}">
|
data-color="{{ station.color }}"
|
||||||
|
data-metadata-url="{{ station.metadata_api_url|default:'' }}">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div class="station-dot me-3" style="background-color: {{ station.color }}; box-shadow: 0 0 15px {{ station.color }};"></div>
|
<div class="station-dot me-3" style="background-color: {{ station.color }}; box-shadow: 0 0 15px {{ station.color }};"></div>
|
||||||
<div class="flex-grow-1">
|
<div class="flex-grow-1">
|
||||||
@ -120,6 +121,11 @@
|
|||||||
background: rgba(255, 255, 255, 0.1);
|
background: rgba(255, 255, 255, 0.1);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
#current-track-info {
|
||||||
|
transition: opacity 0.5s ease-in-out;
|
||||||
|
font-weight: 500;
|
||||||
|
text-shadow: 0 0 10px rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@ -134,10 +140,37 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const visualizer = document.getElementById('visualizer');
|
const visualizer = document.getElementById('visualizer');
|
||||||
const stationItems = document.querySelectorAll('.station-item');
|
const stationItems = document.querySelectorAll('.station-item');
|
||||||
const stationNameDisplay = document.getElementById('current-station-name');
|
const stationNameDisplay = document.getElementById('current-station-name');
|
||||||
|
const trackInfoDisplay = document.getElementById('current-track-info');
|
||||||
const volumeControl = document.getElementById('volume-control');
|
const volumeControl = document.getElementById('volume-control');
|
||||||
const volumeIndicator = document.getElementById('volume-indicator');
|
const volumeIndicator = document.getElementById('volume-indicator');
|
||||||
|
|
||||||
let isPlaying = false;
|
let isPlaying = false;
|
||||||
|
let currentMetadataUrl = '';
|
||||||
|
let metadataInterval = null;
|
||||||
|
|
||||||
|
function fetchMetadata() {
|
||||||
|
if (!currentMetadataUrl) return;
|
||||||
|
|
||||||
|
fetch(currentMetadataUrl)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
let trackText = 'Virtual Broadcast';
|
||||||
|
if (data.artist && data.title) {
|
||||||
|
trackText = `${data.artist} - ${data.title}`;
|
||||||
|
} else if (data.title) {
|
||||||
|
trackText = data.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trackInfoDisplay.textContent !== trackText) {
|
||||||
|
trackInfoDisplay.style.opacity = 0;
|
||||||
|
setTimeout(() => {
|
||||||
|
trackInfoDisplay.textContent = trackText;
|
||||||
|
trackInfoDisplay.style.opacity = 1;
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => console.error('Error fetching metadata:', err));
|
||||||
|
}
|
||||||
|
|
||||||
function togglePlay() {
|
function togglePlay() {
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
@ -146,6 +179,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
vinyl.classList.remove('spinning');
|
vinyl.classList.remove('spinning');
|
||||||
tonearm.classList.remove('active');
|
tonearm.classList.remove('active');
|
||||||
visualizer.classList.remove('playing');
|
visualizer.classList.remove('playing');
|
||||||
|
if (metadataInterval) clearInterval(metadataInterval);
|
||||||
} else {
|
} else {
|
||||||
if (audio.src && audio.src !== window.location.href) {
|
if (audio.src && audio.src !== window.location.href) {
|
||||||
audio.play().catch(e => {
|
audio.play().catch(e => {
|
||||||
@ -156,6 +190,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
vinyl.classList.add('spinning');
|
vinyl.classList.add('spinning');
|
||||||
tonearm.classList.add('active');
|
tonearm.classList.add('active');
|
||||||
visualizer.classList.add('playing');
|
visualizer.classList.add('playing');
|
||||||
|
|
||||||
|
// Start fetching metadata
|
||||||
|
fetchMetadata();
|
||||||
|
metadataInterval = setInterval(fetchMetadata, 15000); // Every 15 seconds
|
||||||
} else {
|
} else {
|
||||||
alert('Please select a station first!');
|
alert('Please select a station first!');
|
||||||
return;
|
return;
|
||||||
@ -171,13 +209,18 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const url = this.dataset.url;
|
const url = this.dataset.url;
|
||||||
const name = this.dataset.name;
|
const name = this.dataset.name;
|
||||||
const color = this.dataset.color;
|
const color = this.dataset.color;
|
||||||
|
currentMetadataUrl = this.dataset.metadataUrl;
|
||||||
|
|
||||||
// Update UI
|
// Update UI
|
||||||
stationItems.forEach(i => i.classList.remove('active'));
|
stationItems.forEach(i => i.classList.remove('active'));
|
||||||
this.classList.add('active');
|
this.classList.add('active');
|
||||||
stationNameDisplay.textContent = name;
|
stationNameDisplay.textContent = name;
|
||||||
vinylLabel.style.boxShadow = `0 0 30px ${color}`;
|
vinylLabel.style.boxShadow = `0 0 30px ${color}`;
|
||||||
|
|
||||||
|
// Reset track info
|
||||||
|
trackInfoDisplay.textContent = 'Connecting...';
|
||||||
|
if (metadataInterval) clearInterval(metadataInterval);
|
||||||
|
|
||||||
// Set source and play
|
// Set source and play
|
||||||
audio.src = url;
|
audio.src = url;
|
||||||
isPlaying = false; // Reset state for toggle
|
isPlaying = false; // Reset state for toggle
|
||||||
@ -191,4 +234,4 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user