diff --git a/music_storage/music/admin.py b/music_storage/music/admin.py index 683b901..60440e1 100644 --- a/music_storage/music/admin.py +++ b/music_storage/music/admin.py @@ -8,6 +8,7 @@ from django.utils.html import format_html from music.models import Track from music.models import Album from music.models import Artist +from music.models import MusicLog from music.models import Playlist from music.models import RecommendationPlaylist @@ -105,3 +106,11 @@ class RecommendationPlaylistAdmin(admin.ModelAdmin): def make_actual(self, request: HttpRequest, queryset: Any) -> None: for recommendation in queryset: recommendation.switch_actual() + + +@admin.register(MusicLog) +class MusicLogAdmin(admin.ModelAdmin): + list_display = ("track", "played_at") + search_fields = ("track__title", "track__album__artist__name", "track__album__name") + list_filter = ("played_at",) + readonly_fields = ("track", "played_at") diff --git a/music_storage/music/api.py b/music_storage/music/api.py new file mode 100644 index 0000000..06b02dc --- /dev/null +++ b/music_storage/music/api.py @@ -0,0 +1,10 @@ +from django.urls import path + +from music.views import TrackAPIView + + +app_name = "music_api" + +urlpatterns = [ + path("tracks//", TrackAPIView.as_view(), name="track_detail"), +] diff --git a/music_storage/music/migrations/0013_musiclog.py b/music_storage/music/migrations/0013_musiclog.py new file mode 100644 index 0000000..5631fd6 --- /dev/null +++ b/music_storage/music/migrations/0013_musiclog.py @@ -0,0 +1,23 @@ +# Generated by Django 6.0 on 2026-01-07 07:40 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('music', '0012_alter_recommendationplaylist_playlist'), + ] + + operations = [ + migrations.CreateModel( + name='MusicLog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('played_at', models.DateTimeField(auto_now_add=True)), + ('user_ip', models.GenericIPAddressField(blank=True, null=True)), + ('track', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='logs', to='music.track')), + ], + ), + ] diff --git a/music_storage/music/models.py b/music_storage/music/models.py index 673ef3d..4d2cedc 100644 --- a/music_storage/music/models.py +++ b/music_storage/music/models.py @@ -116,3 +116,12 @@ class RecommendationPlaylist(BaseModel): .update(is_actual=False)) self.is_actual = True self.save() + + +class MusicLog(models.Model): + track = models.ForeignKey(Track, on_delete=models.CASCADE, related_name="logs") + played_at = models.DateTimeField(auto_now_add=True) + user_ip = models.GenericIPAddressField(null=True, blank=True) + + def __str__(self): + return f"Played {self.track} at {self.played_at}" diff --git a/music_storage/music/views.py b/music_storage/music/views.py index 45123ab..240da3d 100644 --- a/music_storage/music/views.py +++ b/music_storage/music/views.py @@ -1,12 +1,14 @@ from django import views as django_views from django.views.generic import ListView -from django.shortcuts import render +from django.http import JsonResponse from django.http.request import HttpRequest +from django.shortcuts import render from django.shortcuts import get_object_or_404 from music.models import Track from music.models import Artist from music.models import Album +from music.models import MusicLog class TrackListView(ListView): @@ -61,3 +63,24 @@ class AlbumDetailView(django_views.View): def get(self, request: HttpRequest, pk: int, *args, **kwargs): album = get_object_or_404(Album, id=pk) return render(request, "music/album_detail.html", {"album": album}) + + +class TrackAPIView(django_views.View): + def get(self, request: HttpRequest, pk: int, *args, **kwargs): + track = get_object_or_404(Track, id=pk) + data = { + "id": track.id, + "title": track.title, + "url": track.file.url, + "album": { + "id": track.album.id, + "title": track.album.name, + "cover_image": track.album.preview_image.url, + "artist": { + "id": track.album.artist.id, + "name": track.album.artist.name, + }, + }, + } + MusicLog.objects.create(track=track, user_ip=request.META.get("REMOTE_ADDR", "")) + return JsonResponse(data) diff --git a/music_storage/music_storage/urls.py b/music_storage/music_storage/urls.py index 0de84bf..ca6c0da 100644 --- a/music_storage/music_storage/urls.py +++ b/music_storage/music_storage/urls.py @@ -21,4 +21,6 @@ urlpatterns = [ path('admin/', admin.site.urls), path('music/', include("music.urls")), path('', include("core.urls")), + + path('api/music/', include("music.api")), ] diff --git a/music_storage/static/js/player.js b/music_storage/static/js/player.js index 88fadfe..fee0a0d 100644 --- a/music_storage/static/js/player.js +++ b/music_storage/static/js/player.js @@ -53,12 +53,13 @@ document.addEventListener('DOMContentLoaded', function () { updateProgressUI(0, audioPlayer.duration); }); - function updateMediaSession(title, artist) { + function updateMediaSession(title, artist, coverImage) { if (!('mediaSession' in navigator)) return; navigator.mediaSession.metadata = new MediaMetadata({ title: title, - artist: artist + artist: artist, + artwork: [coverImage ? { src: coverImage, sizes: '512x512', type: 'image/png' } : null].filter(Boolean), }); navigator.mediaSession.setActionHandler('play', () => audioPlayer.play()); @@ -81,25 +82,32 @@ document.addEventListener('DOMContentLoaded', function () { currentIndex = index; const item = trackItems[index]; - const src = item.dataset.trackSrc; - const title = item.querySelector('.track-title').textContent; - const artist = item.querySelector('.track-artist').textContent.split(': ')[1]; + const api = item.dataset.trackSrc; + fetch(api) + .then(response => response.json()) + .then(data => { + const src = data.url; + const title = data.title; + const artist = data.album.artist.name; + const coverImage = data.album.cover_image; - resetActiveTrack(); - item.classList.add('active'); - updateUI(title, artist); + resetActiveTrack(); + item.classList.add('active'); + updateUI(title, artist, coverImage); + + // --------------------------- + // ВЫЗОВ Media Session API + updateMediaSession(title, artist, coverImage); + // --------------------------- - // --------------------------- - // ВЫЗОВ Media Session API - // --------------------------- - updateMediaSession(title, artist); - // --------------------------- + audioPlayer.src = src; + audioPlayer.title = artist + ' - ' + title; + audioPlayer.play().catch(err => console.log(err)); - audioPlayer.src = src; - audioPlayer.title = artist + ' - ' + title; - audioPlayer.play().catch(err => console.log(err)); + updatePlayButton(true); - updatePlayButton(true); + }) + .catch(err => console.log(err)); } // Клик по треку diff --git a/music_storage/templates/components/track_list.html b/music_storage/templates/components/track_list.html index 0c060cd..d3c656e 100644 --- a/music_storage/templates/components/track_list.html +++ b/music_storage/templates/components/track_list.html @@ -5,7 +5,7 @@