diff --git a/api/funkwhale_api/subsonic/filters.py b/api/funkwhale_api/subsonic/filters.py
new file mode 100644
index 0000000000000000000000000000000000000000..b7b639fac319bc6f8da17ead83f23f52324c23c7
--- /dev/null
+++ b/api/funkwhale_api/subsonic/filters.py
@@ -0,0 +1,23 @@
+from django_filters import rest_framework as filters
+
+from funkwhale_api.music import models as music_models
+
+
+class AlbumList2FilterSet(filters.FilterSet):
+    type = filters.CharFilter(name='_', method='filter_type')
+
+    class Meta:
+        model = music_models.Album
+        fields = ['type']
+
+    def filter_type(self, queryset, name, value):
+        ORDERING = {
+            'random': '?',
+            'newest': '-creation_date',
+            'alphabeticalByArtist': 'artist__name',
+            'alphabeticalByName': 'title',
+        }
+        if value not in ORDERING:
+            return queryset
+
+        return queryset.order_by(ORDERING[value])
diff --git a/api/funkwhale_api/subsonic/serializers.py b/api/funkwhale_api/subsonic/serializers.py
index 034343be0ca6e65d3af0876ad671b8738364f2e3..59fdb9308f58137ca7b2b5fa879b6319e62dad01 100644
--- a/api/funkwhale_api/subsonic/serializers.py
+++ b/api/funkwhale_api/subsonic/serializers.py
@@ -4,6 +4,16 @@ from django.db.models import functions, Count
 
 from rest_framework import serializers
 
+from funkwhale_api.music import models as music_models
+
+
+def get_artist_data(artist_values):
+    return {
+        'id': artist_values['id'],
+        'name': artist_values['name'],
+        'albumCount': artist_values['_albums_count']
+    }
+
 
 class GetArtistsSerializer(serializers.Serializer):
     def to_representation(self, queryset):
@@ -11,7 +21,7 @@ class GetArtistsSerializer(serializers.Serializer):
             'ignoredArticles': '',
             'index': []
         }
-        queryset = queryset.annotate(_albums_count=Count('albums'))
+        queryset = queryset.with_albums_count()
         queryset = queryset.order_by(functions.Lower('name'))
         values = queryset.values('id', '_albums_count', 'name')
 
@@ -23,11 +33,7 @@ class GetArtistsSerializer(serializers.Serializer):
             letter_data = {
                 'name': letter,
                 'artist': [
-                    {
-                        'id': v['id'],
-                        'name': v['name'],
-                        'albumCount': v['_albums_count']
-                    }
+                    get_artist_data(v)
                     for v in artists
                 ]
             }
@@ -59,42 +65,88 @@ class GetArtistSerializer(serializers.Serializer):
         return payload
 
 
+def get_track_data(album, track, tf):
+    data = {
+        'id': track.pk,
+        'isDir': 'false',
+        'title': track.title,
+        'album': album.title,
+        'artist': album.artist.name,
+        'track': track.position,
+        'contentType': tf.mimetype,
+        'suffix': tf.extension or '',
+        'duration': tf.duration or 0,
+        'created': track.creation_date,
+        'albumId': album.pk,
+        'artistId': album.artist.pk,
+        'type': 'music',
+    }
+    if album.release_date:
+        data['year'] = album.release_date.year
+    return data
+
+
+def get_album2_data(album):
+    payload = {
+        'id': album.id,
+        'artistId': album.artist.id,
+        'name': album.title,
+        'artist': album.artist.name,
+        'created': album.creation_date,
+    }
+    try:
+        payload['songCount'] = album._tracks_count
+    except AttributeError:
+        payload['songCount'] = len(album.tracks.prefetch_related('files'))
+    return payload
+
+
+def get_song_list_data(tracks):
+    songs = []
+    for track in tracks:
+        try:
+            tf = [tf for tf in track.files.all()][0]
+        except IndexError:
+            continue
+        track_data = get_track_data(track.album, track, tf)
+        songs.append(track_data)
+    return songs
+
+
 class GetAlbumSerializer(serializers.Serializer):
     def to_representation(self, album):
-        tracks = album.tracks.prefetch_related('files')
-        payload = {
-            'id': album.id,
-            'artistId': album.artist.id,
-            'name': album.title,
-            'artist': album.artist.name,
-            'created': album.creation_date,
-            'songCount': len(tracks),
-            'song': [],
-        }
+        tracks = album.tracks.prefetch_related('files').select_related('album')
+        payload = get_album2_data(album)
         if album.release_date:
             payload['year'] = album.release_date.year
 
-        for track in tracks:
-            try:
-                tf = [tf for tf in track.files.all()][0]
-            except IndexError:
-                continue
-            track_data = {
-                'id': track.pk,
-                'isDir': False,
-                'title': track.title,
-                'album': album.title,
-                'artist': album.artist.name,
-                'track': track.position,
-                'contentType': tf.mimetype,
-                'suffix': tf.extension,
-                'duration': tf.duration,
-                'created': track.creation_date,
-                'albumId': album.pk,
-                'artistId': album.artist.pk,
-                'type': 'music',
-            }
-            if album.release_date:
-                track_data['year'] = album.release_date.year
-            payload['song'].append(track_data)
+        payload['song'] = get_song_list_data(tracks)
         return payload
+
+
+def get_starred_tracks_data(favorites):
+    by_track_id = {
+        f.track_id: f
+        for f in favorites
+    }
+    tracks = music_models.Track.objects.filter(
+        pk__in=by_track_id.keys()
+    ).select_related('album__artist').prefetch_related('files')
+    tracks = tracks.order_by('-creation_date')
+    data = []
+    for t in tracks:
+        try:
+            tf = [tf for tf in t.files.all()][0]
+        except IndexError:
+            continue
+        td = get_track_data(t.album, t, tf)
+        td['starred'] = by_track_id[t.pk].creation_date
+        data.append(td)
+    return data
+
+
+def get_album_list2_data(albums):
+    return [
+        get_album2_data(a)
+        for a in albums
+    ]
diff --git a/api/funkwhale_api/subsonic/views.py b/api/funkwhale_api/subsonic/views.py
index 98fea7598ec1ccbc879533884e60075c9bfc3ec3..7bb9617953cfa986c7f388f4165b89bfac41fa9b 100644
--- a/api/funkwhale_api/subsonic/views.py
+++ b/api/funkwhale_api/subsonic/views.py
@@ -1,3 +1,7 @@
+import datetime
+
+from django.utils import timezone
+
 from rest_framework import exceptions
 from rest_framework import permissions as rest_permissions
 from rest_framework import response
@@ -5,10 +9,13 @@ from rest_framework import viewsets
 from rest_framework.decorators import list_route
 from rest_framework.serializers import ValidationError
 
+from funkwhale_api.favorites.models import TrackFavorite
 from funkwhale_api.music import models as music_models
+from funkwhale_api.music import utils
 from funkwhale_api.music import views as music_views
 
 from . import authentication
+from . import filters
 from . import negotiation
 from . import serializers
 
@@ -83,6 +90,24 @@ class SubsonicViewSet(viewsets.GenericViewSet):
         }
         return response.Response(data, status=200)
 
+    @list_route(
+        methods=['get', 'post'],
+        url_name='get_license',
+        permissions_classes=[],
+        url_path='getLicense')
+    def get_license(self, request, *args, **kwargs):
+        now = timezone.now()
+        data = {
+            'status': 'ok',
+            'version': '1.16.0',
+            'license': {
+                'valid': 'true',
+                'email': 'valid@valid.license',
+                'licenseExpires': now + datetime.timedelta(days=365)
+            }
+        }
+        return response.Response(data, status=200)
+
     @list_route(
         methods=['get', 'post'],
         url_name='get_artists',
@@ -110,6 +135,19 @@ class SubsonicViewSet(viewsets.GenericViewSet):
 
         return response.Response(payload, status=200)
 
+    @list_route(
+        methods=['get', 'post'],
+        url_name='get_artist_info2',
+        url_path='getArtistInfo2')
+    @find_object(music_models.Artist.objects.all())
+    def get_artist_info2(self, request, *args, **kwargs):
+        artist = kwargs.pop('obj')
+        payload = {
+            'artist-info2': {}
+        }
+
+        return response.Response(payload, status=200)
+
     @list_route(
         methods=['get', 'post'],
         url_name='get_album',
@@ -139,5 +177,134 @@ class SubsonicViewSet(viewsets.GenericViewSet):
         )
         track_file = queryset.first()
         if not track_file:
-            return Response(status=404)
+            return response.Response(status=404)
         return music_views.handle_serve(track_file)
+
+    @list_route(
+        methods=['get', 'post'],
+        url_name='star',
+        url_path='star')
+    @find_object(
+        music_models.Track.objects.all())
+    def star(self, request, *args, **kwargs):
+        track = kwargs.pop('obj')
+        TrackFavorite.add(user=request.user, track=track)
+        return response.Response({'status': 'ok'})
+
+    @list_route(
+        methods=['get', 'post'],
+        url_name='unstar',
+        url_path='unstar')
+    @find_object(
+        music_models.Track.objects.all())
+    def unstar(self, request, *args, **kwargs):
+        track = kwargs.pop('obj')
+        request.user.track_favorites.filter(track=track).delete()
+        return response.Response({'status': 'ok'})
+
+    @list_route(
+        methods=['get', 'post'],
+        url_name='get_starred2',
+        url_path='getStarred2')
+    def get_starred2(self, request, *args, **kwargs):
+        favorites = request.user.track_favorites.all()
+        data = {
+            'song': serializers.get_starred_tracks_data(favorites)
+        }
+        return response.Response(data)
+
+    @list_route(
+        methods=['get', 'post'],
+        url_name='get_album_list2',
+        url_path='getAlbumList2')
+    def get_album_list2(self, request, *args, **kwargs):
+        queryset = music_models.Album.objects.with_tracks_count()
+        data = request.GET or request.POST
+        filterset = filters.AlbumList2FilterSet(data, queryset=queryset)
+        queryset = filterset.qs
+        try:
+            offset = int(data['offset'])
+        except (TypeError, KeyError, ValueError):
+            offset = 0
+
+        try:
+            size = int(data['size'])
+        except (TypeError, KeyError, ValueError):
+            size = 50
+
+        size = min(size, 500)
+        queryset = queryset[offset:size]
+        data = {
+            'albumList2': {
+                'album': serializers.get_album_list2_data(queryset)
+            }
+        }
+        return response.Response(data)
+
+
+    @list_route(
+        methods=['get', 'post'],
+        url_name='search3',
+        url_path='search3')
+    def search3(self, request, *args, **kwargs):
+        data = request.GET or request.POST
+        query = str(data.get('query', '')).replace('*', '')
+        conf = [
+            {
+                'subsonic': 'artist',
+                'search_fields': ['name'],
+                'queryset': (
+                    music_models.Artist.objects
+                                       .with_albums_count()
+                                       .values('id', '_albums_count', 'name')
+                ),
+                'serializer': lambda qs: [
+                    serializers.get_artist_data(a) for a in qs
+                ]
+            },
+            {
+                'subsonic': 'album',
+                'search_fields': ['title'],
+                'queryset': (
+                    music_models.Album.objects
+                                .with_tracks_count()
+                                .select_related('artist')
+                ),
+                'serializer': serializers.get_album_list2_data,
+            },
+            {
+                'subsonic': 'song',
+                'search_fields': ['title'],
+                'queryset': (
+                    music_models.Track.objects
+                                .prefetch_related('files')
+                                .select_related('album__artist')
+                ),
+                'serializer': serializers.get_song_list_data,
+            },
+        ]
+        payload = {
+            'searchResult3': {}
+        }
+        for c in conf:
+            offsetKey = '{}Offset'.format(c['subsonic'])
+            countKey = '{}Count'.format(c['subsonic'])
+            try:
+                offset = int(data[offsetKey])
+            except (TypeError, KeyError, ValueError):
+                offset = 0
+
+            try:
+                size = int(data[countKey])
+            except (TypeError, KeyError, ValueError):
+                size = 20
+
+            size = min(size, 100)
+            queryset = c['queryset']
+            if query:
+                queryset = c['queryset'].filter(
+                    utils.get_query(query, c['search_fields'])
+                )
+            queryset = queryset[offset:size]
+            payload['searchResult3'][c['subsonic']] = c['serializer'](queryset)
+        return response.Response(payload)
diff --git a/api/tests/subsonic/test_serializers.py b/api/tests/subsonic/test_serializers.py
index 3a9de7c9f1385636efd54050c390a705265e46a3..64ecc3b3445d8721e38e80e6d3ae076776a6d21d 100644
--- a/api/tests/subsonic/test_serializers.py
+++ b/api/tests/subsonic/test_serializers.py
@@ -1,3 +1,4 @@
+from funkwhale_api.music import models as music_models
 from funkwhale_api.subsonic import serializers
 
 
@@ -89,15 +90,15 @@ def test_get_album_serializer(factories):
         'song': [
             {
                 'id': track.pk,
-                'isDir': False,
+                'isDir': 'false',
                 'title': track.title,
                 'album': album.title,
                 'artist': artist.name,
                 'track': track.position,
                 'year': track.album.release_date.year,
                 'contentType': tf.mimetype,
-                'suffix': tf.extension,
-                'duration': tf.duration,
+                'suffix': tf.extension or '',
+                'duration': tf.duration or 0,
                 'created': track.creation_date,
                 'albumId': album.pk,
                 'artistId': artist.pk,
@@ -107,3 +108,28 @@ def test_get_album_serializer(factories):
     }
 
     assert serializers.GetAlbumSerializer(album).data == expected
+
+
+def test_starred_tracks2_serializer(factories):
+    artist = factories['music.Artist']()
+    album = factories['music.Album'](artist=artist)
+    track = factories['music.Track'](album=album)
+    tf = factories['music.TrackFile'](track=track)
+    favorite = factories['favorites.TrackFavorite'](track=track)
+    expected = [serializers.get_track_data(album, track, tf)]
+    expected[0]['starred'] = favorite.creation_date
+    data = serializers.get_starred_tracks_data([favorite])
+    assert data == expected
+
+
+def test_get_album_list2_serializer(factories):
+    album1 = factories['music.Album']()
+    album2 = factories['music.Album']()
+
+    qs = music_models.Album.objects.with_tracks_count().order_by('pk')
+    expected = [
+        serializers.get_album2_data(album1),
+        serializers.get_album2_data(album2),
+    ]
+    data = serializers.get_album_list2_data(qs)
+    assert data == expected
diff --git a/api/tests/subsonic/test_views.py b/api/tests/subsonic/test_views.py
index daf4548b05cc7f1c33067cb808046dfd49237f8a..9fd9bfe38126ae7650750ef5082c4cd46a979b57 100644
--- a/api/tests/subsonic/test_views.py
+++ b/api/tests/subsonic/test_views.py
@@ -1,7 +1,10 @@
+import datetime
 import json
 import pytest
 
+from django.utils import timezone
 from django.urls import reverse
+
 from rest_framework.response import Response
 
 from funkwhale_api.music import models as music_models
@@ -42,6 +45,26 @@ def test_exception_wrong_credentials(f, db, api_client):
     assert response.data == expected
 
 
+@pytest.mark.parametrize('f', ['xml', 'json'])
+def test_get_license(f, db, logged_in_api_client, mocker):
+    url = reverse('api:subsonic-get-license')
+    assert url.endswith('getLicense') is True
+    now = timezone.now()
+    mocker.patch('django.utils.timezone.now', return_value=now)
+    response = logged_in_api_client.get(url, {'f': f})
+    expected = {
+        'status': 'ok',
+        'version': '1.16.0',
+        'license': {
+            'valid': 'true',
+            'email': 'valid@valid.license',
+            'licenseExpires': now + datetime.timedelta(days=365)
+        }
+    }
+    assert response.status_code == 200
+    assert response.data == expected
+
+
 @pytest.mark.parametrize('f', ['xml', 'json'])
 def test_ping(f, db, api_client):
     url = reverse('api:subsonic-ping')
@@ -86,6 +109,21 @@ def test_get_artist(f, db, logged_in_api_client, factories):
     assert response.data == expected
 
 
+@pytest.mark.parametrize('f', ['xml', 'json'])
+def test_get_artist_info2(f, db, logged_in_api_client, factories):
+    url = reverse('api:subsonic-get-artist-info2')
+    assert url.endswith('getArtistInfo2') is True
+    artist = factories['music.Artist']()
+
+    expected = {
+        'artist-info2': {}
+    }
+    response = logged_in_api_client.get(url, {'id': artist.pk})
+
+    assert response.status_code == 200
+    assert response.data == expected
+
+
 @pytest.mark.parametrize('f', ['xml', 'json'])
 def test_get_album(f, db, logged_in_api_client, factories):
     url = reverse('api:subsonic-get-album')
@@ -118,3 +156,87 @@ def test_stream(f, db, logged_in_api_client, factories, mocker):
         track_file=tf
     )
     assert response.status_code == 200
+
+
+@pytest.mark.parametrize('f', ['xml', 'json'])
+def test_star(f, db, logged_in_api_client, factories):
+    url = reverse('api:subsonic-star')
+    assert url.endswith('star') is True
+    track = factories['music.Track']()
+    response = logged_in_api_client.get(url, {'f': f, 'id': track.pk})
+
+    assert response.status_code == 200
+    assert response.data == {'status': 'ok'}
+
+    favorite = logged_in_api_client.user.track_favorites.latest('id')
+    assert favorite.track == track
+
+
+@pytest.mark.parametrize('f', ['xml', 'json'])
+def test_unstar(f, db, logged_in_api_client, factories):
+    url = reverse('api:subsonic-unstar')
+    assert url.endswith('unstar') is True
+    track = factories['music.Track']()
+    favorite = factories['favorites.TrackFavorite'](
+        track=track, user=logged_in_api_client.user)
+    response = logged_in_api_client.get(url, {'f': f, 'id': track.pk})
+
+    assert response.status_code == 200
+    assert response.data == {'status': 'ok'}
+    assert logged_in_api_client.user.track_favorites.count() == 0
+
+
+@pytest.mark.parametrize('f', ['xml', 'json'])
+def test_get_starred2(f, db, logged_in_api_client, factories):
+    url = reverse('api:subsonic-get-starred2')
+    assert url.endswith('getStarred2') is True
+    track = factories['music.Track']()
+    favorite = factories['favorites.TrackFavorite'](
+        track=track, user=logged_in_api_client.user)
+    response = logged_in_api_client.get(url, {'f': f, 'id': track.pk})
+
+    assert response.status_code == 200
+    assert response.data == {
+        'song': serializers.get_starred_tracks_data([favorite])
+    }
+
+
+@pytest.mark.parametrize('f', ['xml', 'json'])
+def test_get_album_list2(f, db, logged_in_api_client, factories):
+    url = reverse('api:subsonic-get-album-list2')
+    assert url.endswith('getAlbumList2') is True
+    album1 = factories['music.Album']()
+    album2 = factories['music.Album']()
+    response = logged_in_api_client.get(url, {'f': f, 'type': 'newest'})
+
+    assert response.status_code == 200
+    assert response.data == {
+        'albumList2': {
+            'album': serializers.get_album_list2_data([album2, album1])
+        }
+    }
+
+
+@pytest.mark.parametrize('f', ['xml', 'json'])
+def test_search3(f, db, logged_in_api_client, factories):
+    url = reverse('api:subsonic-search3')
+    assert url.endswith('search3') is True
+    artist = factories['music.Artist'](name='testvalue')
+    factories['music.Artist'](name='nope')
+    album = factories['music.Album'](title='testvalue')
+    factories['music.Album'](title='nope')
+    track = factories['music.Track'](title='testvalue')
+    factories['music.Track'](title='nope')
+
+    response = logged_in_api_client.get(url, {'f': f, 'query': 'testval'})
+
+    artist_qs = music_models.Artist.objects.with_albums_count().filter(
+        pk=artist.pk).values('_albums_count', 'id', 'name')
+    assert response.status_code == 200
+    assert response.data == {
+        'searchResult3': {
+            'artist': [serializers.get_artist_data(a) for a in artist_qs],
+            'album': serializers.get_album_list2_data([album]),
+            'song': serializers.get_song_list_data([track]),
+        }
+    }