* Add jukebox mode Songs can now be played without singing, e.g. as a break filler at parties. - New Jukebox button in the main menu next to Sing and Party, locked without a profile like both of them - Choose all songs, a category or a playlist, random or in order, and optionally only a part of each song (medley part, 30, 60 or 90 seconds) - Video in full screen, lyrics without notes and a short song info whenever a new song starts - Control bar with previous, pause, next, skip intro, audio mode (normal or instrumental), shuffle, repeat, lyrics on/off, queue and "Sing now" - Queue with search: Enter puts a song next, Enter again plays it right away, songs can also be removed and reordered - The queue adapts its size to the free space above the lyrics or the control bar - Control bar and queue fade out after a few seconds without input and come back with the next key press or mouse move - Works with keyboard, mouse and gamepad - Smooth transition between songs, the next song is loaded in advance - "Sing now" starts the current song with the player selection and returns to the jukebox afterwards - The logo in the main menu is now centered - New LyricsIcon and QueueRow textures in all Vocaluxe 2024 skins * Fix SonarCloud issues
303 lines
8.0 KiB
C#
303 lines
8.0 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;
|
|
using System.IO;
|
|
using VocaluxeLib.Draw;
|
|
using VocaluxeLib.Songs;
|
|
|
|
namespace VocaluxeLib.Utils.Player
|
|
{
|
|
public class CSongPlayer : CSoundPlayer
|
|
{
|
|
private readonly object _lock = new object();
|
|
private CSong _Song;
|
|
private bool _VideoEnabled;
|
|
private CVideoStream _Video;
|
|
private CFading _VideoFading;
|
|
|
|
public bool VideoEnabled
|
|
{
|
|
get
|
|
{
|
|
lock (_lock)
|
|
{
|
|
return _VideoEnabled;
|
|
}
|
|
}
|
|
set
|
|
{
|
|
lock (_lock)
|
|
{
|
|
if (_VideoEnabled == value)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_VideoEnabled = value;
|
|
if (_VideoEnabled)
|
|
{
|
|
_LoadVideo();
|
|
}
|
|
else
|
|
{
|
|
_CloseVideo();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public int SongId
|
|
{
|
|
get
|
|
{
|
|
var song = _Song;
|
|
return song == null ? -1 : song.Id;
|
|
}
|
|
}
|
|
|
|
public bool SongHasVideo
|
|
{
|
|
get
|
|
{
|
|
var song = _Song;
|
|
return song != null && !string.IsNullOrEmpty(song.Folder) && !string.IsNullOrEmpty(song.Video) &&
|
|
File.Exists(Path.Combine(song.Folder, song.Video));
|
|
}
|
|
}
|
|
|
|
public override string ArtistAndTitle
|
|
{
|
|
get
|
|
{
|
|
var song = _Song;
|
|
if (song != null && !string.IsNullOrEmpty(song.Artist) && !string.IsNullOrEmpty(song.Title))
|
|
{
|
|
return song.Artist + " - " + song.Title;
|
|
}
|
|
|
|
return base.ArtistAndTitle;
|
|
}
|
|
}
|
|
|
|
public CTextureRef Cover
|
|
{
|
|
get
|
|
{
|
|
var song = _Song;
|
|
return song == null ? CBase.Cover.GetNoCover() : song.CoverTextureBig;
|
|
}
|
|
}
|
|
|
|
public CSongPlayer(bool loop = false) : base(loop) { }
|
|
|
|
public CTextureRef GetVideoTexture()
|
|
{
|
|
lock (_lock)
|
|
{
|
|
var video = _Video;
|
|
var song = _Song;
|
|
if (video == null || video.IsClosed() || song == null || CBase.Video == null || CBase.Sound == null)
|
|
return null;
|
|
|
|
if (CBase.Video.GetFrame(video, CBase.Sound.GetPosition(_StreamId)))
|
|
{
|
|
var texture = video.Texture;
|
|
if (texture != null)
|
|
{
|
|
if (_VideoFading != null)
|
|
{
|
|
bool finished;
|
|
texture.Color.A = _VideoFading.GetValue(out finished);
|
|
if (finished)
|
|
_VideoFading = null;
|
|
}
|
|
return texture;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public void Load(CSong song, float position = 0f, bool autoplay = false)
|
|
{
|
|
if (song == null)
|
|
{
|
|
throw new ArgumentNullException("song");
|
|
}
|
|
|
|
Load(song, song.GetMP3(), true, position, autoplay);
|
|
}
|
|
|
|
public void Load(CSong song, string audioFile, bool loadVideo, float position = 0f, bool autoplay = false)
|
|
{
|
|
if (song == null)
|
|
{
|
|
throw new ArgumentNullException("song");
|
|
}
|
|
|
|
lock (_lock)
|
|
{
|
|
Load(audioFile, position, autoplay);
|
|
_Song = song;
|
|
if (loadVideo)
|
|
{
|
|
_LoadVideo();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void SetPosition(float position)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
Position = position;
|
|
var video = _Video;
|
|
var song = _Song;
|
|
if (video != null && !video.IsClosed() && song != null)
|
|
{
|
|
CBase.Video?.Skip(video, position, song.VideoGap);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void _LoadVideo()
|
|
{
|
|
lock (_lock)
|
|
{
|
|
var song = _Song;
|
|
if (song == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_Video != null || !SongHasVideo)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var videoFilePath = Path.Combine(song.Folder, song.Video);
|
|
var video = CBase.Video?.Load(videoFilePath);
|
|
if (video == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_Video = video;
|
|
_VideoFading = new CFading(0f, 1f, 3f);
|
|
|
|
if (IsPlaying)
|
|
{
|
|
CBase.Video?.Skip(video, Position, song.VideoGap);
|
|
CBase.Video?.Resume(video);
|
|
}
|
|
else
|
|
{
|
|
CBase.Video?.Skip(video, 0f, song.VideoGap);
|
|
}
|
|
}
|
|
}
|
|
|
|
public override bool Play()
|
|
{
|
|
lock (_lock)
|
|
{
|
|
if (!base.Play())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var video = _Video;
|
|
var song = _Song;
|
|
if (video != null && !video.IsClosed() && song != null)
|
|
{
|
|
CBase.Video?.Skip(video, Position, song.VideoGap);
|
|
CBase.Video?.Resume(video);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public override bool Pause()
|
|
{
|
|
lock (_lock)
|
|
{
|
|
if (!base.Pause())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var video = _Video;
|
|
if (video != null && !video.IsClosed())
|
|
{
|
|
CBase.Video?.Pause(video);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public override bool Stop()
|
|
{
|
|
lock (_lock)
|
|
{
|
|
if (!base.Stop())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var video = _Video;
|
|
var song = _Song;
|
|
if (video != null && !video.IsClosed())
|
|
{
|
|
CBase.Video?.Pause(video);
|
|
if (song != null)
|
|
{
|
|
CBase.Video?.Skip(video, 0f, song.VideoGap);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private void _CloseVideo()
|
|
{
|
|
lock (_lock)
|
|
{
|
|
if (_Video != null)
|
|
{
|
|
CBase.Video?.Close(ref _Video);
|
|
_Video = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void Close()
|
|
{
|
|
lock (_lock)
|
|
{
|
|
base.Close();
|
|
|
|
_Song = null;
|
|
_CloseVideo();
|
|
}
|
|
}
|
|
}
|
|
} |