Loops
- Looping is now done by the playback backends instead of restarting
the stream with Stop/Play. Loop can be toggled at runtime (SetLoop in
ISound/IPlayback/IAudioStream), so the repeat button keeps working.
- GStreamer: gapless loops via playbin's about-to-finish signal.
- PortAudio/OpenAL: the decoder is reopened at the end of the file
(Acinerella's seek does not reset FFmpeg after EOF) while the buffered
data keeps playing, so the loop has no gap. The reported position wraps
correctly.
Credits screen
- New event music player in CBackgroundMusic that replaces the current
background music and resumes it afterwards.
- The credits screen loops Sounds/CreditsMusic.mp3
("Atmosphere Calm Background" by lvymusic, Pixabay Content License).
- As the credits screen was touched anyway, its content was updated as
well.
Fixes for PortAudio/OpenAL playback found on the way
- Acinerella delivers 32 bit float samples for mp3, so every file was
rejected with "Unsupported BitDepth". Float samples are converted to
16 bit now.
- Acinerella reports a missing frame (EAGAIN) like the end of the file,
so decoding is retried a few times before treating it as the end.
- OpenAL stopped after half a second because the decoder thread was
never woken up to refill its buffer. A source that ran out of data is
restarted as well.
Part of #677
Co-authored-by: Florian Ostertag <flokuep@users.noreply.github.com>
194 lines
5.1 KiB
C#
194 lines
5.1 KiB
C#
#region license
|
|
// This file is part of Vocaluxe.
|
|
//
|
|
// Vocaluxe is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Vocaluxe is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Vocaluxe. If not, see <http://www.gnu.org/licenses/>.
|
|
#endregion
|
|
|
|
using System.IO;
|
|
|
|
namespace VocaluxeLib.Utils.Player
|
|
{
|
|
public class CSoundPlayer
|
|
{
|
|
protected int _StreamId = -1;
|
|
protected readonly float _FadeTime = CBase.Settings.GetSoundPlayerFadeTime();
|
|
public string FilePath { get; private set; }
|
|
|
|
private bool _Loop;
|
|
|
|
public bool Loop
|
|
{
|
|
get { return _Loop; }
|
|
set
|
|
{
|
|
_Loop = value;
|
|
if (SoundLoaded)
|
|
{
|
|
CBase.Sound.SetLoop(_StreamId, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the current stream position or sets it
|
|
/// </summary>
|
|
public float Position
|
|
{
|
|
set
|
|
{
|
|
if (!SoundLoaded)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CBase.Sound.SetPosition(_StreamId, value);
|
|
}
|
|
get { return !SoundLoaded ? -1 : CBase.Sound.GetPosition(_StreamId); }
|
|
}
|
|
|
|
public float Length
|
|
{
|
|
get { return !SoundLoaded ? -1 : CBase.Sound.GetLength(_StreamId); }
|
|
}
|
|
|
|
public bool IsPlaying { get; private set; }
|
|
|
|
public bool IsFinished
|
|
{
|
|
get { return !Loop && (CBase.Sound.IsFinished(_StreamId) || !IsPlaying); }
|
|
}
|
|
|
|
public bool SoundLoaded
|
|
{
|
|
get { return _StreamId != -1; }
|
|
}
|
|
|
|
public virtual string ArtistAndTitle
|
|
{
|
|
get { return string.IsNullOrEmpty(FilePath) ? "" : Path.GetFileNameWithoutExtension(FilePath); }
|
|
}
|
|
|
|
public CSoundPlayer(bool loop = false)
|
|
{
|
|
Loop = loop;
|
|
}
|
|
|
|
public void Load(string file, float position = -1f, bool autoplay = false)
|
|
{
|
|
Close();
|
|
|
|
_StreamId = CBase.Sound.Load(file, Loop, true);
|
|
if (_StreamId < 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FilePath = file;
|
|
if (position > 0f)
|
|
{
|
|
Position = position;
|
|
}
|
|
|
|
if (autoplay)
|
|
{
|
|
Play();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts or resumes the player
|
|
/// </summary>
|
|
/// <returns>True if state changed, false if nothing loaded or already playing</returns>
|
|
public virtual bool Play()
|
|
{
|
|
if (!SoundLoaded || IsPlaying)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CBase.Sound.SetStreamVolume(_StreamId, 0);
|
|
CBase.Sound.Fade(_StreamId, 100, _FadeTime);
|
|
CBase.Sound.Play(_StreamId);
|
|
IsPlaying = true;
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pauses the player
|
|
/// </summary>
|
|
/// <returns>True if state changed, false if nothing loaded or already paused</returns>
|
|
public virtual bool Pause()
|
|
{
|
|
if (!SoundLoaded || CBase.Sound.IsPaused(_StreamId))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CBase.Sound.Fade(_StreamId, 0, _FadeTime, EStreamAction.Pause);
|
|
IsPlaying = false;
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stops the player (no playback and position is set to start)
|
|
/// </summary>
|
|
/// <returns>True if playback was stopped</returns>
|
|
public virtual bool Stop()
|
|
{
|
|
if (!SoundLoaded)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CBase.Sound.Fade(_StreamId, 0, _FadeTime, EStreamAction.Stop);
|
|
IsPlaying = false;
|
|
return true;
|
|
}
|
|
|
|
public virtual void Close()
|
|
{
|
|
if (!SoundLoaded)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CBase.Sound.Fade(_StreamId, 0, _FadeTime, EStreamAction.Close);
|
|
_StreamId = -1;
|
|
FilePath = "";
|
|
IsPlaying = false;
|
|
}
|
|
|
|
public void Update()
|
|
{
|
|
if (!IsPlaying)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (Loop)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var finished = CBase.Sound.IsFinished(_StreamId);
|
|
var len = CBase.Sound.GetLength(_StreamId);
|
|
var timeToPlay = len > 0f ? len - CBase.Sound.GetPosition(_StreamId) : _FadeTime + 1f;
|
|
|
|
if (timeToPlay <= _FadeTime || finished)
|
|
{
|
|
Stop();
|
|
}
|
|
}
|
|
}
|
|
} |