Exemplo n.º 1
0
        /// <summary>
        /// Returns a PLS file format for streaming playlists, used for stations like TuneIn Radio.
        /// </summary>
        /// <returns></returns>
        public async Task <IActionResult> TuneInV3()
        {
            // Build the PLS file.
            // PLS format is very simple: https://en.wikipedia.org/wiki/PLS_(file_format)
            var plsBuilder = new StringBuilder();

            plsBuilder.AppendLine("[playlist]"); // The header

            var songsWithRanking = await DbSession.Query <Song, Songs_RankStandings>()
                                   .As <Songs_RankStandings.Result>()
                                   .ToListAsync();

            var       userPrefs      = new UserSongPreferences();
            const int totalSongCount = 25;
            var       songPicks      = Enumerable
                                       .Range(0, totalSongCount)
                                       .Select(s => userPrefs.PickSong(songsWithRanking))
                                       .ToList();
            var songs = await DbSession.LoadWithoutNulls <Song>(songPicks.Select(p => p.SongId));

            for (var i = 0; i < songs.Count; i++)
            {
                var oneBasedIndex = i + 1;

                // File1=http://thefile.mp3
                plsBuilder.AppendLine();
                plsBuilder.Append("File");
                plsBuilder.Append(oneBasedIndex);
                plsBuilder.Append('=');
                plsBuilder.AppendLine(songs[i].Uri.ToString());

                // Title1=Shema Yisrael by Barry & Batya Segal
                plsBuilder.Append("Title");
                plsBuilder.Append(oneBasedIndex);
                plsBuilder.Append('=');
                var songName = string.IsNullOrEmpty(songs[i].HebrewName) ? songs[i].Name : songs[i].Name + " " + songs[i].HebrewName;
                plsBuilder.Append(songName);
                plsBuilder.Append(" by ");
                plsBuilder.AppendLine(songs[i].Artist);
            }

            // NumberOfEntries=20
            plsBuilder.AppendLine();
            plsBuilder.Append("NumberOfEntries");
            plsBuilder.Append('=');
            plsBuilder.Append(songs.Count);

            var plsBytes = Encoding.UTF8.GetBytes(plsBuilder.ToString());

            return(File(plsBytes, "application/pls+xml", "ChavahTuneInStream.pls"));
        }
Exemplo n.º 2
0
        public async Task <Song> ChooseSong()
        {
            // HOT PATH: This method greatly impacts the UI. The user waits for this method before ever hearing a song.
            // We want to send back the next song ASAP.

            var userPreferences  = default(UserSongPreferences);
            var songsWithRanking = default(IList <Songs_RankStandings.Result>);

            // Aggressive caching for the UserSongPreferences and SongsWithRanking. These don't change often.
            using (DbSession.Advanced.DocumentStore.AggressivelyCacheFor(TimeSpan.FromDays(1)))
            {
                var user = await GetUser();

                // This is NOT an unbounded result set:
                // This queries the Songs_RankStandings index, which will reduce the results. Max number of results will be the number of CommunityRankStanding enum constants.
                songsWithRanking = await DbSession.Query <Song, Songs_RankStandings>()
                                   .As <Songs_RankStandings.Result>()
                                   .ToListAsync();

                if (user != null)
                {
                    userPreferences = await DbSession.Query <Like, Likes_SongPreferences>()
                                      .As <UserSongPreferences>()
                                      .FirstOrDefaultAsync(u => u.UserId == user.Id);
                }
                if (userPreferences == null)
                {
                    userPreferences = new UserSongPreferences();
                }
            }

            // Run the song picking algorithm.
            var songPick = userPreferences.PickSong(songsWithRanking);

            if (string.IsNullOrEmpty(songPick.SongId))
            {
                logger.LogWarning("Chose song but ended up with an empty Song ID.", songPick);
                return(await PickRandomSong());
            }

            var song = await DbSession.LoadRequiredAsync <Song>(songPick.SongId);

            var songLikeDislike = userPreferences.Songs.FirstOrDefault(s => s.SongId == song.Id);
            var songLikeStatus  = songLikeDislike?.LikeCount > 0 ?
                                  LikeStatus.Like : songLikeDislike?.DislikeCount > 0 ?
                                  LikeStatus.Dislike : LikeStatus.None;

            return(song.ToDto(songLikeStatus, songPick));
        }
Exemplo n.º 3
0
        public async Task <List <string> > GetPrefsDebug(string email)
        {
            var stopWatch = new System.Diagnostics.Stopwatch();

            stopWatch.Start();

            var userId          = "AppUsers/" + email;
            var userPreferences = await DbSession.Query <Like, Likes_SongPreferences>()
                                  .As <UserSongPreferences>()
                                  .FirstOrDefaultAsync(u => u.UserId == userId);

            var userPrefsTime = stopWatch.Elapsed;

            stopWatch.Restart();

            if (userPreferences == null)
            {
                userPreferences = new UserSongPreferences();
            }

            var songsWithRanking = await DbSession.Query <Song, Songs_RankStandings>()
                                   .As <Songs_RankStandings.Result>()
                                   .ToListAsync();

            var rankingTime = stopWatch.Elapsed;

            stopWatch.Restart();

            var table = userPreferences.BuildSongWeightsTable(songsWithRanking);

            var tableTime = stopWatch.Elapsed;

            stopWatch.Stop();

            var songsOrderedByWeight = table
                                       .Select(s => (SongId: s.Key, s.Value.Weight, s.Value.ArtistMultiplier, s.Value.AlbumMultiplier, SongMultipler: s.Value.SongMultiplier, s.Value.TagMultiplier, RankMultiplier: s.Value.CommunityRankMultiplier, s.Value.AgeMultiplier))
                                       .OrderByDescending(s => s.Weight)
                                       .Select(s => $"Song ID {s.SongId}, Weight {s.Weight}, Artist multiplier: {s.ArtistMultiplier}, Album multipler: {s.AlbumMultiplier}, Song multiplier: {s.SongMultipler}, Tag multiplier {s.TagMultiplier}, Rank multiplier: {s.RankMultiplier}, Age multiplier: {s.AgeMultiplier}")
                                       .ToList();

            songsOrderedByWeight.Insert(0, $"Performance statistics: Total query time {tableTime + rankingTime + userPrefsTime}. Querying user prefs {userPrefsTime}, querying ranking {rankingTime}, building table {tableTime}");

            return(songsOrderedByWeight);
        }
Exemplo n.º 4
0
        public async Task <ActionResult> GetNextSong()
        {
            var userPreferences  = new UserSongPreferences();
            var songsWithRanking = default(IList <Songs_RankStandings.Result>);

            // Aggressive caching for the UserSongPreferences and SongsWithRanking. These don't change often.
            using (var cache = DbSession.Advanced.DocumentStore.AggressivelyCacheFor(TimeSpan.FromDays(1)))
            {
                // This is NOT an unbounded result set:
                // This queries the Songs_RankStandings index, which will reduce the results. Max number of results will be the number of CommunityRankStanding enum constants.
                songsWithRanking = await DbSession.Query <Song, Songs_RankStandings>()
                                   .As <Songs_RankStandings.Result>()
                                   .ToListAsync();
            }

            var songPick = userPreferences.PickSong(songsWithRanking);
            var song     = await DbSession.LoadRequiredAsync <Song>(songPick.SongId);

            return(Redirect(song.Uri.ToString()));
        }
Exemplo n.º 5
0
        public async Task <List <Song> > ChooseSongBatch()
        {
            const int songsInBatch     = 5;
            var       userPreferences  = default(UserSongPreferences);
            var       songsWithRanking = default(IList <Songs_RankStandings.Result>);

            // Aggressive caching for the UserSongPreferences and SongsWithRanking. These don't change often.
            using (var cache = DbSession.Advanced.DocumentStore.AggressivelyCacheFor(TimeSpan.FromDays(1)))
            {
                var user = await GetUser();

                // This is NOT an unbounded result set:
                // This queries the Songs_RankStandings index, which will reduce the results. Max number of results will be the number of CommunityRankStanding enum constants.
                songsWithRanking = await DbSession.Query <Song, Songs_RankStandings>()
                                   .As <Songs_RankStandings.Result>()
                                   .ToListAsync();

                if (user != null)
                {
                    userPreferences = await DbSession.Query <Like, Likes_SongPreferences>()
                                      .As <UserSongPreferences>()
                                      .FirstOrDefaultAsync(u => u.UserId == user.Id);
                }
                if (userPreferences == null)
                {
                    userPreferences = new UserSongPreferences();
                }
            }

            // Run the song picking algorithm.
            var batch       = new List <Song>(songsInBatch);
            var pickedSongs = Enumerable.Range(0, songsInBatch)
                              .Select(_ => userPreferences.PickSong(songsWithRanking))
                              .ToList();

            if (pickedSongs.Any(s => string.IsNullOrEmpty(s.SongId)))
            {
                logger.LogWarning("Picked songs for batch, but returned one or more empty song IDs {pickedSongs}", pickedSongs);
            }

            // Make a single trip to the database to load all the picked songs.
            var pickedSongIds = pickedSongs
                                .Select(s => s.SongId)
                                .ToList();
            var songs = await DbSession
                        .LoadWithoutNulls <Song>(pickedSongIds);

            var songDtos = new List <Song>(songs.Count);

            for (var i = 0; i < songs.Count; i++)
            {
                var song = songs[i];
                if (song != null)
                {
                    var pickReasons     = pickedSongs[i];
                    var songLikeDislike = userPreferences.Songs.FirstOrDefault(s => s.SongId == song.Id);
                    var songLikeStatus  = songLikeDislike?.LikeCount > 0 ?
                                          LikeStatus.Like : songLikeDislike?.DislikeCount > 0 ?
                                          LikeStatus.Dislike : LikeStatus.None;
                    var dto = song.ToDto(songLikeStatus, pickReasons);
                    songDtos.Add(dto);
                }
            }

            return(songDtos);
        }