Esempio n. 1
0
    def _handle_autoplay(self, url=None):
        if self.autoplay and models.QueuedSong.objects.count() == 0:
            if url is None:
                # if no url was specified, use the one of the current song
                try:
                    current_song = models.CurrentSong.objects.get()
                    url = current_song.external_url
                except (models.CurrentSong.DoesNotExist,
                        models.CurrentSong.MultipleObjectsReturned):
                    return

            provider = SongProvider.create(self.musiq, external_url=url)
            try:
                suggestion = provider.get_suggestion()
            except Exception as e:
                self.musiq.base.logger.error('error during suggestions for ' +
                                             url)
                self.musiq.base.logger.error(e)
            else:
                self.musiq._request_music('',
                                          suggestion,
                                          None,
                                          False,
                                          provider.type,
                                          archive=False,
                                          manually_requested=False)
Esempio n. 2
0
    def _handle_autoplay(self, url: Optional[str] = None) -> None:
        if self.autoplay and models.QueuedSong.objects.count() == 0:
            if url is None:
                # if no url was specified, use the one of the current song
                try:
                    current_song = models.CurrentSong.objects.get()
                    url = current_song.external_url
                except (
                    models.CurrentSong.DoesNotExist,
                    models.CurrentSong.MultipleObjectsReturned,
                ):
                    return

            provider = SongProvider.create(self.musiq, external_url=url)
            try:
                suggestion = provider.get_suggestion()
                # The player loop is not restarted after error automatically.
                # As this function can raise several exceptions (it might do networking)
                # we catch every exception to make sure the loop keeps running
            except Exception as e:  # pylint: disable=broad-except
                logging.exception("error during suggestions for " + url)
            else:
                self.musiq.do_request_music(
                    "",
                    suggestion,
                    None,
                    False,
                    provider.type,
                    archive=False,
                    manually_requested=False,
                )
Esempio n. 3
0
    def _request_music(self, ip, query, key, playlist, platform, archive=True, manually_requested=True):
        providers = []

        if playlist:
            if key is not None:
                # an archived song was requested. The key determines the SongProvider (Youtube or Spotify)
                provider = PlaylistProvider.create(self, query, key)
                if provider is None:
                    return HttpResponseBadRequest('No provider found for requested playlist')
                providers.append(provider)
            else:
                # try to use spotify if the user did not specifically request youtube
                if platform is None or platform == 'spotify':
                    if self.base.settings.spotify_enabled:
                        providers.append(SpotifyPlaylistProvider(self, query, key))
                # use Youtube as a fallback
                providers.append(YoutubePlaylistProvider(self, query, key))
        else:
            if key is not None:
                # an archived song was requested. The key determines the SongProvider (Youtube or Spotify)
                provider = SongProvider.create(self, query, key)
                if provider is None:
                    return HttpResponseBadRequest('No provider found for requested song')
                providers.append(provider)
            else:
                # try to use spotify if the user did not specifically request youtube
                if platform is None or platform == 'spotify':
                    if self.base.settings.spotify_enabled:
                        providers.append(SpotifySongProvider(self, query, key))
                # use Youtube as a fallback
                providers.append(YoutubeSongProvider(self, query, key))

        fallback = False
        used_provider = None
        for i, provider in enumerate(providers):
            if not provider.check_cached():
                if not provider.check_downloadable():
                    # this provider cannot provide this song, use the next provider
                    # if this was the last provider, show its error
                    if i == len(providers) - 1:
                        return HttpResponseBadRequest(provider.error)
                    fallback = True
                    continue
                if not provider.download(ip, archive=archive, manually_requested=manually_requested):
                    return HttpResponseBadRequest(provider.error)
            else:
                provider.enqueue(ip, archive=archive, manually_requested=manually_requested)
            # the current provider could provide the song, don't try the other ones
            used_provider = provider
            break
        message = used_provider.ok_message
        if fallback:
            message = message + ' (used fallback)'
        return HttpResponse(message)
Esempio n. 4
0
    def request_radio(self, request):
        # only get ip on user requests
        if self.base.settings.logging_enabled:
            ip, is_routable = ipware.get_client_ip(request)
            if ip is None:
                ip = ''
        else:
            ip = ''

        try:
            current_song = CurrentSong.objects.get()
        except CurrentSong.DoesNotExist:
            return HttpResponseBadRequest('Need a song to play the radio')
        provider = SongProvider.create(self, external_url=current_song.external_url)
        return provider.request_radio(ip)
Esempio n. 5
0
    def request_radio(self, request: WSGIRequest) -> HttpResponse:
        """Endpoint to request radio for the current song."""
        # only get ip on user requests
        if self.base.settings.logging_enabled:
            request_ip, _ = ipware.get_client_ip(request)
            if request_ip is None:
                request_ip = ""
        else:
            request_ip = ""

        try:
            current_song = CurrentSong.objects.get()
        except CurrentSong.DoesNotExist:
            return HttpResponseBadRequest("Need a song to play the radio")
        provider = SongProvider.create(self, external_url=current_song.external_url)
        return provider.request_radio(request_ip)
Esempio n. 6
0
    def get_suggestions(self, request: WSGIRequest) -> JsonResponse:
        """Returns suggestions for a given query.
        Combines online and offline suggestions."""
        terms = request.GET["term"].split()
        suggest_playlist = request.GET["playlist"] == "true"

        results: List[Dict[str, Union[str, int]]] = []

        if self.musiq.base.settings.has_internet:
            if self.musiq.base.settings.spotify_enabled:
                spotify_suggestions = Spotify().get_search_suggestions(
                    " ".join(terms), suggest_playlist)
                spotify_suggestions = spotify_suggestions[:2]
                for suggestion, external_url in spotify_suggestions:
                    results.append({
                        "key": external_url,
                        "value": suggestion,
                        "type": "spotify-online",
                    })

            if self.musiq.base.settings.soundcloud_enabled:
                spotify_suggestions = Soundcloud().get_search_suggestions(
                    " ".join(terms))
                soundcloud_suggestions = spotify_suggestions[:2]
                for suggestion in soundcloud_suggestions:
                    results.append({
                        "key": -1,
                        "value": suggestion,
                        "type": "soundcloud-online"
                    })

            if self.musiq.base.settings.youtube_enabled:
                youtube_suggestions = Youtube().get_search_suggestions(
                    " ".join(terms))
                # limit to the first three online suggestions
                youtube_suggestions = youtube_suggestions[:2]
                for suggestion in youtube_suggestions:
                    results.append({
                        "key": -1,
                        "value": suggestion,
                        "type": "youtube-online"
                    })

        # The following query is roughly equivalent to the following SQL code:
        # SELECT DISTINCT id, title, artist, counter
        # FROM core_archivedsong s LEFT JOIN core_archivedquery q ON q.song
        # WHERE forall term in terms: term in q.query or term in s.artist or term in s.title
        # ORDER BY -counter
        if suggest_playlist:
            remaining_playlists = ArchivedPlaylist.objects.prefetch_related(
                "queries")
            # exclude radios from suggestions
            remaining_playlists = remaining_playlists.exclude(
                list_id__startswith="RD").exclude(list_id__contains="&list=RD")

            for term in terms:
                remaining_playlists = remaining_playlists.filter(
                    Q(title__icontains=term)
                    | Q(queries__query__icontains=term))

            playlist_suggestions = (remaining_playlists.values(
                "id", "title", "counter").distinct().order_by("-counter")[:20])

            for playlist in playlist_suggestions:
                archived_playlist = ArchivedPlaylist.objects.get(
                    id=playlist["id"])
                result_dict: Dict[str, Union[str, int]] = {
                    "key": playlist["id"],
                    "value": playlist["title"],
                    "counter": playlist["counter"],
                    "type":
                    song_utils.determine_playlist_type(archived_playlist),
                }
                results.append(result_dict)
        else:
            remaining_songs = ArchivedSong.objects.prefetch_related("queries")

            for term in terms:
                remaining_songs = remaining_songs.filter(
                    Q(title__icontains=term)
                    | Q(artist__icontains=term)
                    | Q(queries__query__icontains=term))

            song_suggestions = (remaining_songs.values(
                "id", "title", "url", "artist",
                "counter").distinct().order_by("-counter")[:20])

            for song in song_suggestions:
                provider = SongProvider.create(self.musiq,
                                               external_url=song["url"])
                cached = provider.check_cached()
                # don't suggest local songs if they are not cached (=not at expected location)
                if not cached and provider.type == "local":
                    continue
                # don't suggest online songs when we don't have internet
                if not self.musiq.base.settings.has_internet and not cached:
                    continue
                # don't suggest youtube songs if it was disabled
                if (not self.musiq.base.settings.youtube_enabled
                        and provider.type == "youtube"):
                    continue
                # don't suggest spotify songs if we are not logged in
                if (not self.musiq.base.settings.spotify_enabled
                        and provider.type == "spotify"):
                    continue
                # don't suggest soundcloud songs if we are not logged in
                if (not self.musiq.base.settings.soundcloud_enabled
                        and provider.type == "soundcloud"):
                    continue
                result_dict = {
                    "key": song["id"],
                    "value": song_utils.displayname(song["artist"],
                                                    song["title"]),
                    "counter": song["counter"],
                    "type": provider.type,
                }
                results.append(result_dict)

        return JsonResponse(results, safe=False)
Esempio n. 7
0
    def do_request_music(
        self,
        request_ip: str,
        query: str,
        key: Optional[int],
        playlist: bool,
        platform: str,
        archive: bool = True,
        manually_requested: bool = True,
    ) -> Tuple[bool, str, Optional[int]]:
        """Performs the actual requesting of the music, not an endpoint.
        Enqueues the requested song or playlist into the queue, using appropriate providers.
        Returns a 3-tuple: successful, message, queue_key"""
        providers: List[MusicProvider] = []

        provider: MusicProvider
        if playlist:
            if key is not None:
                # an archived song was requested.
                # The key determines the PlaylistProvider
                provider = PlaylistProvider.create(self, query, key)
                if provider is None:
                    return False, "No provider found for requested playlist", None
                providers.append(provider)
            else:
                # try to use spotify if the user did not specifically request youtube
                if self.base.settings.soundcloud_enabled:
                    soundcloud_provider = SoundcloudPlaylistProvider(self, query, key)
                    if platform == "soundcloud":
                        providers.insert(0, soundcloud_provider)
                    else:
                        providers.append(soundcloud_provider)
                if self.base.settings.spotify_enabled:
                    spotify_provider = SpotifyPlaylistProvider(self, query, key)
                    if platform == "spotify":
                        providers.insert(0, spotify_provider)
                    else:
                        providers.append(spotify_provider)
                if self.base.settings.youtube_enabled:
                    youtube_provider = YoutubePlaylistProvider(self, query, key)
                    if platform == "youtube":
                        providers.insert(0, youtube_provider)
                    else:
                        providers.append(youtube_provider)
        else:
            if key is not None:
                # an archived song was requested.
                # The key determines the SongProvider
                provider = SongProvider.create(self, query, key)
                if provider is None:
                    return False, "No provider found for requested song", None
                providers.append(provider)
            else:
                if platform == "local":
                    # if a local provider was requested,
                    # use only this one as its only possible source is the database
                    providers.append(LocalSongProvider(self, query, key))
                else:
                    if self.base.settings.soundcloud_enabled:
                        try:
                            soundcloud_provider = SoundcloudSongProvider(
                                self, query, key
                            )
                            if platform == "soundcloud":
                                providers.insert(0, soundcloud_provider)
                            else:
                                providers.append(soundcloud_provider)
                        except WrongUrlError:
                            pass
                    if self.base.settings.spotify_enabled:
                        try:
                            spotify_provider = SpotifySongProvider(self, query, key)
                            if platform == "spotify":
                                providers.insert(0, spotify_provider)
                            else:
                                providers.append(spotify_provider)
                        except WrongUrlError:
                            pass
                    if self.base.settings.youtube_enabled:
                        try:
                            youtube_provider = YoutubeSongProvider(self, query, key)
                            if platform == "youtube":
                                providers.insert(0, youtube_provider)
                            else:
                                providers.append(youtube_provider)
                        except WrongUrlError:
                            pass

        if not len(providers):
            return False, "No backend configured to handle your request.", None

        fallback = False
        for i, provider in enumerate(providers):
            try:
                provider.request(
                    request_ip, archive=archive, manually_requested=manually_requested
                )
                # the current provider could provide the song, don't try the other ones
                break
            except ProviderError:
                # this provider cannot provide this song, use the next provider
                # if this was the last provider, show its error
                if i == len(providers) - 1:
                    return False, provider.error, None
                fallback = True
        message = provider.ok_message
        queue_key = None
        if not playlist:
            queue_key = provider.queued_song.id
        if fallback:
            message += " (used fallback)"
        return True, message, queue_key
Esempio n. 8
0
    def get_suggestions(self, request: WSGIRequest) -> JsonResponse:
        """Returns suggestions for a given query.
        Combines online and offline suggestions."""
        query = request.GET["term"]
        suggest_playlist = request.GET["playlist"] == "true"

        results: List[Dict[str, Union[str, int]]] = []

        if (self.musiq.base.settings.online_suggestions
                and self.musiq.base.settings.has_internet):

            number_of_suggestions = 2
            if [
                    self.musiq.base.settings.spotify_enabled,
                    self.musiq.base.settings.soundcloud_enabled,
                    self.musiq.base.settings.youtube_enabled,
            ].count(True) > 1:
                # If there is more than one active service,
                # only offer one online suggestion per service
                number_of_suggestions = 1

            if self.musiq.base.settings.spotify_enabled:
                spotify_suggestions = Spotify().get_search_suggestions(
                    query, suggest_playlist)
                spotify_suggestions = spotify_suggestions[:
                                                          number_of_suggestions]
                for suggestion, external_url in spotify_suggestions:
                    results.append({
                        "key": external_url,
                        "value": suggestion,
                        "type": "spotify-online",
                    })

            if self.musiq.base.settings.soundcloud_enabled:
                soundcloud_suggestions = Soundcloud().get_search_suggestions(
                    query)
                soundcloud_suggestions = soundcloud_suggestions[:
                                                                number_of_suggestions]
                for suggestion in soundcloud_suggestions:
                    results.append({
                        "key": -1,
                        "value": suggestion,
                        "type": "soundcloud-online"
                    })

            if self.musiq.base.settings.youtube_enabled:
                youtube_suggestions = Youtube().get_search_suggestions(query)
                # limit to the first three online suggestions
                youtube_suggestions = youtube_suggestions[:
                                                          number_of_suggestions]
                for suggestion in youtube_suggestions:
                    results.append({
                        "key": -1,
                        "value": suggestion,
                        "type": "youtube-online"
                    })

        if suggest_playlist:
            search_results = watson.search(query,
                                           models=(ArchivedPlaylist, ))[:20]

            for playlist in search_results:
                playlist_info = playlist.meta
                archived_playlist = ArchivedPlaylist.objects.get(
                    id=playlist_info["id"])
                result_dict: Dict[str, Union[str, int]] = {
                    "key": playlist_info["id"],
                    "value": playlist_info["title"],
                    "counter": playlist_info["counter"],
                    "type":
                    song_utils.determine_playlist_type(archived_playlist),
                }
                results.append(result_dict)
        else:
            search_results = watson.search(query, models=(ArchivedSong, ))[:20]

            for search_result in search_results:
                song_info = search_result.meta
                provider = SongProvider.create(self.musiq,
                                               external_url=song_info["url"])
                cached = provider.check_cached()
                # don't suggest local songs if they are not cached (=not at expected location)
                if not cached and provider.type == "local":
                    continue
                # don't suggest online songs when we don't have internet
                if not self.musiq.base.settings.has_internet and not cached:
                    continue
                # don't suggest youtube songs if it was disabled
                if (not self.musiq.base.settings.youtube_enabled
                        and provider.type == "youtube"):
                    continue
                # don't suggest spotify songs if we are not logged in
                if (not self.musiq.base.settings.spotify_enabled
                        and provider.type == "spotify"):
                    continue
                # don't suggest soundcloud songs if we are not logged in
                if (not self.musiq.base.settings.soundcloud_enabled
                        and provider.type == "soundcloud"):
                    continue
                result_dict = {
                    "key":
                    song_info["id"],
                    "value":
                    song_utils.displayname(song_info["artist"],
                                           song_info["title"]),
                    "counter":
                    song_info["counter"],
                    "type":
                    provider.type,
                }
                results.append(result_dict)

        return JsonResponse(results, safe=False)
Esempio n. 9
0
    def get_suggestions(self, request: WSGIRequest) -> JsonResponse:
        """Returns suggestions for a given query.
        Combines online and offline suggestions."""
        query = request.GET["term"]
        suggest_playlist = request.GET["playlist"] == "true"

        results = self._online_suggestions(query, suggest_playlist)
        basic_settings = self.musiq.base.settings.basic

        if suggest_playlist:
            search_results = watson.search(query, models=(
                ArchivedPlaylist, ))[:basic_settings.number_of_suggestions]

            for playlist in search_results:
                playlist_info = playlist.meta
                archived_playlist = ArchivedPlaylist.objects.get(
                    id=playlist_info["id"])
                result_dict: Dict[str, Union[str, int]] = {
                    "key": playlist_info["id"],
                    "value": playlist_info["title"],
                    "counter": playlist.object.counter,
                    "type":
                    song_utils.determine_playlist_type(archived_playlist),
                }
                results.append(result_dict)
        else:
            search_results = watson.search(
                query,
                models=(ArchivedSong, ))[:basic_settings.number_of_suggestions]

            for search_result in search_results:
                song_info = search_result.meta
                provider = SongProvider.create(self.musiq,
                                               external_url=song_info["url"])
                cached = provider.check_cached()
                # don't suggest local songs if they are not cached (=not at expected location)
                if not cached and provider.type == "local":
                    continue
                # don't suggest online songs when we don't have internet
                if not self.musiq.base.settings.basic.has_internet and not cached:
                    continue
                # don't suggest youtube songs if it was disabled
                if (not self.musiq.base.settings.platforms.youtube_enabled
                        and provider.type == "youtube"):
                    continue
                # don't suggest spotify songs if we are not logged in
                if (not self.musiq.base.settings.platforms.spotify_enabled
                        and provider.type == "spotify"):
                    continue
                # don't suggest soundcloud songs if we are not logged in
                if (not self.musiq.base.settings.platforms.soundcloud_enabled
                        and provider.type == "soundcloud"):
                    continue
                result_dict = {
                    "key":
                    song_info["id"],
                    "value":
                    song_utils.displayname(song_info["artist"],
                                           song_info["title"]),
                    "counter":
                    search_result.object.counter,
                    "type":
                    provider.type,
                }
                results.append(result_dict)

        return JsonResponse(results, safe=False)
Esempio n. 10
0
    def do_request_music(
        self,
        request_ip: str,
        query: str,
        key: Optional[int],
        playlist: bool,
        platform: str,
        archive: bool = True,
        manually_requested: bool = True,
    ) -> HttpResponse:
        """Performs the actual requesting of the music, not an endpoint.
        Enqueues the requested song or playlist into the queue, using appropriate providers."""
        providers: List[MusicProvider] = []

        provider: MusicProvider
        if playlist:
            if key is not None:
                # an archived song was requested.
                # The key determines the SongProvider (Youtube or Spotify)
                provider = PlaylistProvider.create(self, query, key)
                if provider is None:
                    return HttpResponseBadRequest(
                        "No provider found for requested playlist")
                providers.append(provider)
            else:
                # try to use spotify if the user did not specifically request youtube
                if platform is None or platform == "spotify":
                    if self.base.settings.spotify_enabled:
                        providers.append(
                            SpotifyPlaylistProvider(self, query, key))
                if self.base.settings.youtube_enabled:
                    # use Youtube as a fallback
                    providers.append(YoutubePlaylistProvider(self, query, key))
        else:
            if key is not None:
                # an archived song was requested.
                # The key determines the SongProvider (Youtube or Spotify)
                provider = SongProvider.create(self, query, key)
                if provider is None:
                    return HttpResponseBadRequest(
                        "No provider found for requested song")
                providers.append(provider)
            else:
                if platform == "local":
                    # if a local provider was requested,
                    # use only this one as its only possible source is the database
                    providers.append(LocalSongProvider(self, query, key))
                else:
                    # try to use spotify if the user did not specifically request youtube
                    if platform is None or platform == "spotify":
                        if self.base.settings.spotify_enabled:
                            try:
                                providers.append(
                                    SpotifySongProvider(self, query, key))
                            except ValueError:
                                pass
                    if self.base.settings.youtube_enabled:
                        # use Youtube as a fallback
                        providers.append(YoutubeSongProvider(self, query, key))

        if not len(providers):
            return HttpResponseBadRequest(
                "No backend configured to handle your request.")

        fallback = False
        for i, provider in enumerate(providers):
            if not provider.check_cached():
                if not provider.check_downloadable():
                    # this provider cannot provide this song, use the next provider
                    # if this was the last provider, show its error
                    if i == len(providers) - 1:
                        return HttpResponseBadRequest(provider.error)
                    fallback = True
                    continue
                if not provider.download(
                        request_ip,
                        archive=archive,
                        manually_requested=manually_requested):
                    return HttpResponseBadRequest(provider.error)
            else:
                provider.enqueue(request_ip,
                                 archive=archive,
                                 manually_requested=manually_requested)
            # the current provider could provide the song, don't try the other ones
            used_provider = provider
            break
        message = provider.ok_message
        if fallback:
            message += " (used fallback)"
        return HttpResponse(message)
Esempio n. 11
0
    def get_suggestions(self, request):
        terms = request.GET['term'].split()
        suggest_playlist = request.GET['playlist'] == 'true'

        results = []
        if suggest_playlist:
            remaining_playlists = ArchivedPlaylist.objects.prefetch_related(
                'queries')
            # exclude radios from suggestions
            remaining_playlists = remaining_playlists.exclude(
                list_id__startswith='RD').exclude(list_id__contains='&list=RD')

            for term in terms:
                remaining_playlists = remaining_playlists.filter(
                    Q(title__icontains=term)
                    | Q(queries__query__icontains=term))

            remaining_playlists = remaining_playlists \
                                      .values('id', 'title', 'counter') \
                                      .distinct() \
                                      .order_by('-counter') \
                [:20]

            for playlist in remaining_playlists:
                cached = False
                archived_playlist = ArchivedPlaylist.objects.get(
                    id=playlist['id'])
                result_dict = {
                    'key': playlist['id'],
                    'value': playlist['title'],
                    'counter': playlist['counter'],
                    'type':
                    song_utils.determine_playlist_type(archived_playlist),
                }
                results.append(result_dict)
        else:
            remaining_songs = ArchivedSong.objects.prefetch_related('queries')

            for term in terms:
                remaining_songs = remaining_songs.filter(
                    Q(title__icontains=term) | Q(artist__icontains=term)
                    | Q(queries__query__icontains=term))

            remaining_songs = remaining_songs \
                                  .values('id', 'title', 'url', 'artist', 'counter') \
                                  .distinct() \
                                  .order_by('-counter') \
                [:20]

            for song in remaining_songs:
                provider = SongProvider.create(self.musiq,
                                               external_url=song['url'])
                cached = provider.check_cached()
                # don't suggest local songs if they are not cached (=not at expected location)
                if not cached and provider.type == 'local':
                    continue
                # don't suggest online songs when we don't have internet
                if not self.musiq.base.settings.has_internet and not cached:
                    continue
                # don't suggest spotify songs if we are not logged in
                if not self.musiq.base.settings.spotify_enabled and provider.type == 'spotify':
                    continue
                result_dict = {
                    'key': song['id'],
                    'value': song_utils.displayname(song['artist'],
                                                    song['title']),
                    'counter': song['counter'],
                    'type': provider.type
                }
                results.append(result_dict)

        return HttpResponse(json.dumps(results))
        """ query for the suggestions
Esempio n. 12
0
    def _loop(self):
        while True:

            catch_up = None
            if models.CurrentSong.objects.exists():
                # recover interrupted song from database
                current_song = models.CurrentSong.objects.get()

                # continue with the current song (approximately) where we last left
                song_provider = SongProvider.create(self.musiq, internal_url=current_song.internal_url)
                duration = song_provider.get_metadata()['duration']
                catch_up = round((timezone.now() - current_song.created).total_seconds() * 1000)
                if catch_up > duration * 1000:
                    catch_up = -1
            else:
                self.queue_semaphore.acquire()

                # select the next song depending on settings
                if self.musiq.base.settings.voting_system:
                    with transaction.atomic():
                        song = self.queue.all().order_by('-votes', 'index')[0]
                        song_id = song.id
                        self.queue.remove(song.id)
                elif self.shuffle:
                    index = random.randint(0,models.QueuedSong.objects.count() - 1)
                    song_id = models.QueuedSong.objects.all()[index].id
                    song = self.queue.remove(song_id)
                else:
                    # move the first song in the queue into the current song
                    song_id, song = self.queue.dequeue()

                if song is None:
                    # either the semaphore didn't match up with the actual count of songs in the queue or a race condition occured
                    self.musiq.base.logger.info('dequeued on empty list')
                    continue
                

                current_song = models.CurrentSong.objects.create(
                        queue_key=song_id,
                        manually_requested=song.manually_requested,
                        votes=song.votes,
                        internal_url=song.internal_url,
                        external_url=song.external_url,
                        artist=song.artist,
                        title=song.title,
                        duration=song.duration,
                )

                self._handle_autoplay()

                try:
                    archived_song = models.ArchivedSong.objects.get(url=current_song.external_url)
                    if self.musiq.base.settings.voting_system:
                        votes = current_song.votes
                    else:
                        votes = None
                    if self.musiq.base.settings.logging_enabled:
                        models.PlayLog.objects.create(
                                song=archived_song,
                                manually_requested=current_song.manually_requested,
                                votes=votes)
                except (models.ArchivedSong.DoesNotExist, models.ArchivedSong.MultipleObjectsReturned):
                    pass

            self.musiq.update_state()

            playing = Event()
            @self.player.on_event('playback_state_changed')
            def on_playback_state_changed(event):
                playing.set()

            with self.mopidy_command(important=True):
                # after a restart consume may be set to False again, so make sure it is on
                self.player.tracklist.clear()
                self.player.tracklist.set_consume(True)
                self.player.tracklist.add(uris=[current_song.internal_url])
                self.player.playback.play()
                # mopidy can only seek when the song is playing
                playing.wait(timeout=1)
                if catch_up is not None and catch_up >= 0:
                    self.player.playback.seek(catch_up)

            self.musiq.update_state()

            if catch_up is None or catch_up >= 0:
                if not self._wait_until_song_end():
                    # there was a ConnectionError during waiting for the song to end
                    # thus, we do not delete the current song but recover its state by restarting the loop
                    continue

            current_song.delete()

            if self.repeat:
                song_provider = SongProvider.create(self.musiq, internal_url=current_song.internal_url)
                self.queue.enqueue(song_provider.get_metadata(), False)
                self.queue_semaphore.release()
            else:
                # the song left the queue, we can delete big downloads
                song_utils.decide_deletion(current_song.internal_url)

            self.musiq.update_state()

            if self.musiq.base.user_manager.partymode_enabled() and random.random() < self.musiq.base.settings.alarm_probability:
                self.alarm_playing.set()
                self.musiq.base.lights.alarm_started()

                self.musiq.update_state()

                with self.mopidy_command(important=True):
                    self.player.tracklist.add(uris=['file://'+os.path.join(settings.BASE_DIR, 'config/sounds/alarm.m4a')])
                    self.player.playback.play()
                playing.clear()
                playing.wait(timeout=1)
                self._wait_until_song_end()

                self.musiq.base.lights.alarm_stopped()
                self.musiq.update_state()
                self.alarm_playing.clear()
Esempio n. 13
0
    def _loop(self) -> None:
        """The main loop of the player.
        Takes a song from the queue and plays it until it is finished."""
        while True:

            catch_up = None
            if models.CurrentSong.objects.exists():
                # recover interrupted song from database
                current_song = models.CurrentSong.objects.get()

                # continue with the current song (approximately) where we last left
                song_provider = SongProvider.create(
                    self.musiq, external_url=current_song.external_url)
                duration = int(song_provider.get_metadata()["duration"])
                catch_up = round(
                    (timezone.now() - current_song.created).total_seconds() *
                    1000)
                if catch_up > duration * 1000:
                    catch_up = -1
            else:
                self.queue_semaphore.acquire()
                if not self.running:
                    break

                # select the next song depending on settings
                song: Optional[models.QueuedSong]
                if self.musiq.base.settings.basic.voting_system:
                    with transaction.atomic():
                        song = self.queue.confirmed().order_by(
                            "-votes", "index")[0]
                        song_id = song.id
                        self.queue.remove(song.id)
                elif self.musiq.controller.shuffle:
                    confirmed = self.queue.confirmed()
                    index = random.randint(0, confirmed.count() - 1)
                    song_id = confirmed[index].id
                    song = self.queue.remove(song_id)
                else:
                    # move the first song in the queue into the current song
                    song_id, song = self.queue.dequeue()

                if song is None:
                    # either the semaphore didn't match up with the actual count
                    # of songs in the queue or a race condition occured
                    logging.warning("dequeued on empty list")
                    continue

                current_song = models.CurrentSong.objects.create(
                    queue_key=song_id,
                    manually_requested=song.manually_requested,
                    votes=song.votes,
                    internal_url=song.internal_url,
                    external_url=song.external_url,
                    artist=song.artist,
                    title=song.title,
                    duration=song.duration,
                )

                self.handle_autoplay()

                try:
                    archived_song = models.ArchivedSong.objects.get(
                        url=current_song.external_url)
                    votes: Optional[int]
                    if self.musiq.base.settings.basic.voting_system:
                        votes = current_song.votes
                    else:
                        votes = None
                    if self.musiq.base.settings.basic.logging_enabled:
                        models.PlayLog.objects.create(
                            song=archived_song,
                            manually_requested=current_song.manually_requested,
                            votes=votes,
                        )
                except (
                        models.ArchivedSong.DoesNotExist,
                        models.ArchivedSong.MultipleObjectsReturned,
                ):
                    pass

            self.musiq.update_state()

            playing = Event()

            @self.player.on_event("playback_state_changed")
            def _on_playback_state_changed(_event):
                playing.set()

            with self.mopidy_command(important=True):
                # after a restart consume may be set to False again, so make sure it is on
                self.player.tracklist.clear()
                self.player.tracklist.set_consume(True)
                self.player.tracklist.add(uris=[current_song.internal_url])
                self.player.playback.play()
                # mopidy can only seek when the song is playing
                playing.wait(timeout=1)
                if catch_up is not None and catch_up >= 0:
                    self.player.playback.seek(catch_up)

            self.musiq.update_state()

            if catch_up is None or catch_up >= 0:
                if not self._wait_until_song_end():
                    # there was a ConnectionError during waiting for the song to end
                    # we do not delete the current song but recover its state by restarting the loop
                    continue

            current_song.delete()

            if self.musiq.controller.repeat:
                song_provider = SongProvider.create(
                    self.musiq, external_url=current_song.external_url)
                self.queue.enqueue(song_provider.get_metadata(), False)
                self.queue_semaphore.release()

            self.musiq.update_state()

            if (self.musiq.base.user_manager.partymode_enabled()
                    and random.random() <
                    self.musiq.base.settings.basic.alarm_probability):
                self.alarm_playing.set()
                self.musiq.base.lights.alarm_started()

                self.musiq.update_state()

                with self.mopidy_command(important=True):
                    self.player.tracklist.add(uris=[
                        "file://" + os.path.join(settings.BASE_DIR,
                                                 "config/sounds/alarm.m4a")
                    ])
                    self.player.playback.play()
                playing.clear()
                playing.wait(timeout=1)
                self._wait_until_song_end()

                self.musiq.base.lights.alarm_stopped()
                self.musiq.update_state()
                self.alarm_playing.clear()