public void AddTrack(FmShellArguments args) { LOGGER.Info("Adding track by file path"); var audioSource = new Mp3FileAudioSource(args.Args[0].ToString()); RadioCastServer.AddTrack(audioSource); }
private static Task StartRadioFeeder(RadioCastServer radioCastServer, MongoWrapper mongoWrapper) { var userCollection = mongoWrapper.Database.GetCollection <Models.Musician>(nameof(Models.User)); var songSortBuilder = new SortDefinitionBuilder <ProjectedMusicianSong>(); var songSort = songSortBuilder .Descending(nameof(ProjectedMusicianSong.Score)); var userFilterBuilder = new FilterDefinitionBuilder <Models.Musician>(); var userFilter = userFilterBuilder.And ( GeneralUtils.NotDeactivated(userFilterBuilder), userFilterBuilder.AnyEq("_t", nameof(Models.Musician)) ); var songFilterBuilder = new FilterDefinitionBuilder <ProjectedMusicianSong>(); var songFilter = songFilterBuilder.And ( GeneralUtils.NotDeactivated(songFilterBuilder, s => s.Song), songFilterBuilder.Eq(s => s.Song.RadioAuthorized, true), songFilterBuilder.Eq(s => s.Song.Original, true) ); var projectionBuilder = new ProjectionDefinitionBuilder <Models.Musician>(); var projection = projectionBuilder.Include(m => m.FullName).Include(m => m.Songs); var fsBucket = new GridFSBucket <ObjectId>(mongoWrapper.Database); var trackHistory = new List <(IAudioSource, ProjectedMusicianSong)>(); var onTrackChangedTE = new ManualResetEvent(true); radioCastServer.OnTrackChanged += async(s, e) => { List <(IAudioSource, ProjectedMusicianSong)> myTrackHistory; lock (trackHistory) { myTrackHistory = trackHistory.ToList(); } RadioInfoController.CurrentSong = myTrackHistory.Where(th => th.Item1.Equals(e.NewTrack)).Select(th => th.Item2).LastOrDefault(); LOGGER.Info("Now playing: {}", JsonConvert.SerializeObject(RadioInfoController.CurrentSong)); onTrackChangedTE.Set(); if (e.OldTrack == null) { return; } var oldTrack = myTrackHistory.Where(th => th.Item1.Equals(e.OldTrack)).Select(th => th.Item2).LastOrDefault(); var oldMusicianId = oldTrack._id; var oldTrackId = oldTrack.Song._id; var musSongFilterBuilder = new FilterDefinitionBuilder <Models.Musician>(); var musSongFilter = musSongFilterBuilder.And ( musSongFilterBuilder.Eq(m => m._id, oldMusicianId), musSongFilterBuilder.ElemMatch(m => m.Songs, sg => sg._id == oldTrackId) ); var musSongUpdate = new UpdateDefinitionBuilder <Models.Musician>() .Inc($"{nameof(Models.Musician.Songs)}.$.{nameof(Song.TimesPlayedRadio)}", 1); await userCollection.UpdateOneAsync(musSongFilter, musSongUpdate); // Remove oldest, only keep 5 in history if (myTrackHistory.Count > 5) { lock (trackHistory) { trackHistory.RemoveAt(0); } } }; return(Task.Run(async() => { while (true) { List <(IAudioSource, ProjectedMusicianSong)> myTrackHistory; lock (trackHistory) { myTrackHistory = trackHistory.ToList(); } var lookupStageRandomArr = $@" {{ $lookup: {{ from: ""randomNumbers"", pipeline: [ {{ $sample: {{ size: 2 }} }} ], as: ""RandomArr"" }} }} "; // OK Vezes totais que a música foi tocada * 0.5 // OK Vezes totais que a música foi tocada na rádio * -1 // OK Se música está presente na lista das últimas 5 tocadas, -100 // OK Se o autor da música está presente na lista das últimas 5 tocadas, -50 // OK Pontuação aleatória para cada música, entre - 10 e + 10 // OK Número de dias desde o cadastramento da música * -1 // OK Há uma chance de 5% de multiplicar a pontuação resultante por -1 (efeito nostalgia) var scoreStage = $@" {{ $addFields: {{ ""Score"": {{ $add: [ {{ $multiply: [ ""$Song.timesPlayed"", 0.5 ] }}, {{ $multiply: [ ""$Song.timesPlayedRadio"", -1 ] }}, {{ $cond: {{ if: {{ $in: [ ""$_id"", [ {myTrackHistory.Select(th => $"ObjectId(\"{th.Item2._id.ToString()}\")").DefaultIfEmpty("").Aggregate((s1, s2) => $"{s1.TrimEnd(',')},{s2.TrimEnd(',')},").TrimEnd(',')} ] ] }}, then: -50, else: 0 }} }}, {{ $cond: {{ if: {{ $in: [ ""$Song._id"", [ {myTrackHistory.Select(th => $"ObjectId(\"{th.Item2.Song._id.ToString()}\")").DefaultIfEmpty("").Aggregate((s1, s2) => $"{s1.TrimEnd(',')},{s2.TrimEnd(',')},").TrimEnd(',')} ] ] }}, then: -100, else: 0 }} }}, {{ $add: [ {{ $multiply: [ {{ $toDecimal: {{ $arrayElemAt: [""$RandomArr.decimal"", 0] }} }}, 21 ] }}, -10 ] }}, {{ $multiply: [ {{ $divide: [ {{ $subtract: [ {{ $toDate: ""{DateTime.UtcNow.ToString("o")}"" }}, {{ $toDate: ""$Song._id"" }} ] }}, NumberLong(""86400000"") ] }}, {{ $cond: {{ if: {{ $gt: [ {{ $toDecimal: {{ $arrayElemAt: [""$RandomArr.decimal"", 1] }} }}, NumberDecimal(""0.1"") ] }}, then: -1, else: 1 }} }} ] }} ] }} }} }}"; LOGGER.Info("Score stage generated MongoDB query: {}", scoreStage); var pipeline = PipelineDefinitionBuilder .For <Models.Musician>() .Match(userFilter) .Unwind(m => m.Songs, new AggregateUnwindOptions <Models.Musician> { PreserveNullAndEmptyArrays = false, IncludeArrayIndex = null, }) .Project(m => new ProjectedMusicianSong { _id = m._id, Song = (Song)m.Songs, Score = 1, }) .Match(songFilter) .AppendStage <Musician, ProjectedMusicianSong, ProjectedMusicianSong>(lookupStageRandomArr) .AppendStage <Musician, ProjectedMusicianSong, ProjectedMusicianSong>(scoreStage) .Sort(songSort) .Limit(1); if (LOGGER.IsDebugEnabled) { LOGGER.Debug("Pipeline generated MongoDB query for song: {}", pipeline.ToString()); } var findTask = userCollection.AggregateAsync(pipeline, new AggregateOptions { AllowDiskUse = true, BatchSize = 1, UseCursor = true, Comment = "Radio Aggregate Query", TranslationOptions = new ExpressionTranslationOptions { StringTranslationMode = AggregateStringTranslationMode.CodePoints } }); var findResult = await findTask; var firstSong = findResult.SingleOrDefault(); // If no songs, wait a minute before checking again // Mostly not to strain the CPU on development environments if (firstSong?.Song == null) { Thread.Sleep(TimeSpan.FromMinutes(1)); continue; } LOGGER.Info("Next selected song: {}", JsonConvert.SerializeObject(firstSong)); var audioRef = firstSong.Song.AudioReference; var gridId = audioRef._id; var fileStreamTask = fsBucket.OpenDownloadStreamAsync(gridId, new GridFSDownloadOptions { Seekable = true, CheckMD5 = false, }); var audioSource = new Mp3FileAudioSource(await fileStreamTask, firstSong.Song.Name); // Wait for the radio to need more songs before we add the track we have on our hands while (radioCastServer.TrackCount > 1) { onTrackChangedTE.WaitOne(); onTrackChangedTE.Reset(); } lock (trackHistory) { trackHistory.Add((audioSource, firstSong)); } radioCastServer.AddTrack(audioSource); } })); }