/// <summary>Loads the specified <see cref="MDBPlayListItem"/>.</summary> /// <param name="mdb">The <see cref="MusicDataBase"/> instance.</param> /// <param name="item">The <see cref="MDBPlayListItem"/>.</param> /// <returns></returns> public static MDBFileSelection Load(MusicDataBase mdb, MDBPlayListItem item) { var result = new MDBFileSelection(); result.AudioFile = mdb.AudioFiles.TryGetStruct(item.AudioFileID); result.File = mdb.Files.TryGetStruct(result.AudioFile.FileID); result.NowPlaying = MDBNowPlaying.Create(mdb, item.StreamID, item.OwnerID, item.SubsetID, DateTime.MinValue, result.AudioFile); result.PlayListItem = item; return(result); }
private void PlayFile(MDBFileSelection selection) { var silenceCompression = mdb.Config.ReadBool("Player", "SilenceCompression", false); IAudioDecoder decoder = new Mpg123(false); if (!decoder.IsAvailable) { decoder = new MP3AudioDecoder(); } this.LogInfo("Prepare playing {0} using audio decoder <cyan>{1}<default> silence compression {2}", selection.AudioFile, decoder, silenceCompression); string fileName = selection.File.GetFullPath(mdb); this.LogDebug("Open file {0}", fileName); var mp3FileStream = ResistantFileStream.OpenSequentialRead(fileName); decoder.BeginDecode(mp3FileStream); currentNowPlaying = MDBNowPlaying.Create(mdb, selection.PlayListItem.StreamID, selection.PlayListItem.OwnerID, selection.PlayListItem.SubsetID, DateTime.MinValue, selection.AudioFile); IAudioData audioData = decoder.Decode(); if (device == null) { device = SelectDevice() ?? throw new Exception("Could not find any available audio device implementation!"); } var audioOut = device.CreateAudioOut(audioData); this.LogInfo("Start buffering {0} <cyan>{1}", selection.AudioFile, audioData); TimeSpan inSilenceTime = TimeSpan.Zero; TimeSpan fileReadPosition = TimeSpan.Zero; skip = false; bool started = false; long underflow = 0; while (!exit && !skip) { //buffer until we got at least one second, or ten during playback var sleepTime = started ? audioOut.TimeBuffered - TenSeconds : audioOut.TimeBuffered - OneSecond; //buffer filled ? if (sleepTime > TimeSpan.Zero) { if (CurrentStreamSettings.StreamType != MDBStreamType.JukeBob) { skip = true; } if (audioOut.Volume != Math.Max(0, CurrentStreamSettings.Volume)) { audioOut.Volume = CurrentStreamSettings.Volume; } //already started ? if (started) { //yes, check for a gap/buffer underrun ? if (audioOut.BufferUnderflowCount != underflow) { //we got a gap, fix starttime underflow = audioOut.BufferUnderflowCount; this.LogWarning("Player GAP {0}, Buffer was empty!", underflow); currentNowPlaying.StartDateTime = DateTime.UtcNow - fileReadPosition + audioOut.TimeBuffered; ThreadPool.QueueUserWorkItem(delegate { mdb.NowPlaying.Replace(currentNowPlaying); }); } else { Thread.Sleep(Math.Min(1000, (int)sleepTime.TotalMilliseconds)); } } else { //do start if we are allowed to (check playing previous title) sleepTime = nextStart - DateTime.UtcNow; if (sleepTime > TimeSpan.Zero) { this.LogVerbose("Sleep {0}", sleepTime.FormatTime()); Thread.Sleep(sleepTime); } currentNowPlaying.StartDateTime = DateTime.UtcNow; audioOut.Start(); started = true; this.LogInfo("Start playing {0}", selection.AudioFile); //write to now playing ThreadPool.QueueUserWorkItem(delegate { mdb.NowPlaying.Replace(currentNowPlaying); mdb.PlayListItems.TryDelete(nameof(MDBPlayListItem.ID), selection.PlayListItem.ID); }); } } audioData = decoder.Decode(); //end of file ? if (audioData == null) { break; } //add packet duration to file position fileReadPosition += audioData.Duration; //skip silence if (silenceCompression) { if (audioData.Peak < 0.001f) { if (inSilenceTime > OneSecond) { continue; } inSilenceTime += audioData.Duration; } else { if (inSilenceTime > OneSecond) { //we skipped some silence, fix starttime this.LogDebug("Silence compression {0}", inSilenceTime.FormatTime()); currentNowPlaying.StartDateTime = DateTime.UtcNow - fileReadPosition + audioOut.TimeBuffered; ThreadPool.QueueUserWorkItem(delegate { mdb.NowPlaying.Replace(currentNowPlaying); }); } inSilenceTime = TimeSpan.Zero; } } if (!audioOut.Configuration.Equals(audioData)) { this.LogWarning("Frankenstein Stream in file <red>{0}", fileName); break; } audioOut.Write(audioData); } nextStart = DateTime.UtcNow; if (skip) { //skipped, start in 2s nextStart += TimeSpan.FromSeconds(1); } else { //start after current title nextStart += audioOut.TimeBuffered - TimeSpan.FromSeconds(1); } this.LogInfo("Finish playing {0}", selection.AudioFile); if (exit) { CloseAudioOut(audioOut); } else { CloseAudioOutAsync(audioOut); } }
/// <summary>Selects the next file.</summary> /// <param name="streamID">The stream identifier.</param> /// <returns>Returns true on success, false otherwise</returns> public MDBFileSelection SelectNextFile(long streamID) { var config = mdb.GetStreamSettings(streamID); MDBSubset subset = mdb.Subsets.TryGetStruct(config.SubsetID); if (subset.ID == 0) { subset.Name = "Undefined"; } int seqNumber = mdb.Subsets.SequenceNumber ^ mdb.SubsetFilters.SequenceNumber ^ mdb.AudioFiles.SequenceNumber; if (audioFileIDs == null || seqNumber != sequenceNumber) { audioFileIDs = mdb.GetSubsetAudioFileIDs(subset.ID, config.MinimumLength, config.MaximumLength); if (subset.TitleCount != audioFileIDs.Count) { subset.TitleCount = audioFileIDs.Count; if (subset.ID > 0) { mdb.Subsets.Update(subset); } } sequenceNumber = seqNumber; this.LogInfo("Reloaded subset {0} at player", subset); } if (subset.ID > 0 && subset.TitleCount != audioFileIDs.Count) { subset.TitleCount = audioFileIDs.Count; try { mdb.Subsets.Update(subset); this.LogInfo("Subset {0} title count updated!", subset); } catch { } } if (audioFileIDs.Count == 0) { this.LogDebug("No subset defined or subset result empty for stream <red>{0}<default> selecting random titles.", streamID); audioFileIDs = mdb.AudioFiles.IDs; } if (audioFileIDs.Count == 0) { this.LogDebug("No audio files found!"); Selection = null; return(null); } var listSearch = Search.FieldEquals(nameof(MDBPlayListItem.StreamID), streamID); while (true) { Func <long> getCount = () => mdb.PlayListItems.Count(listSearch); //fill playlist for (int n = 0; getCount() < config.MinimumTitleCount; n++) { //max 4 tries per slot if (n > config.MinimumTitleCount * 4) { break; } int i = (int)((rnd.Next() * (long)rnd.Next()) % audioFileIDs.Count); long nextID = audioFileIDs[i]; //audiofile valid ? MDBAudioFile audioFile; if (!mdb.AudioFiles.TryGetStruct(nextID, out audioFile)) { continue; } //file does not exist if (!File.Exists(mdb.Files.TryGetStruct(audioFile.FileID).GetFullPath(mdb))) { mdb.AudioFiles.TryDelete(audioFile.FileID); mdb.Files.TryDelete(audioFile.FileID); this.LogError("AudioFile <red>{0}<default> removed (inaccessible).", audioFile); continue; } //yes, playlist contains id already ? if (mdb.PlayListItems.Exist(listSearch & Search.FieldEquals(nameof(MDBPlayListItem.AudioFileID), nextID))) { continue; } //no add mdb.PlayListItems.Insert(new MDBPlayListItem() { AudioFileID = audioFile.FileID, StreamID = streamID, SubsetID = subset.ID, Added = DateTime.UtcNow.AddTicks(DefaultRNG.Int8), }); this.LogInfo("Added audio file {0} from subset {1} to {2} playlist.", audioFile, subset, streamID); } mdb.Save(); //get current entry try { var items = mdb.PlayListItems.GetStructs( listSearch & Search.FieldGreater(nameof(MDBPlayListItem.OwnerID), 0), ResultOption.SortAscending(nameof(MDBPlayListItem.Added)) + ResultOption.Limit(1)); if (items.Count == 0) { items = mdb.PlayListItems.GetStructs( listSearch & Search.FieldEquals(nameof(MDBPlayListItem.OwnerID), 0), ResultOption.SortAscending(nameof(MDBPlayListItem.Added)) + ResultOption.Limit(1)); } var item = items.FirstOrDefault(); if (item.ID == 0) { continue; } try { var result = Selection = MDBFileSelection.Load(mdb, item); return(result); } finally { //always remove playlistitem (even on errors) mdb.PlayListItems.TryDelete(item.ID); } } catch (Exception ex) { this.LogError(ex, "Cannot start stream {0}!", streamID, ex.Message); Selection = null; return(null); } } }