Refactors the music player.

This commit is contained in:
Zimmermann Gyula
2015-06-26 02:45:43 +02:00
parent f5d7df621f
commit 60fafaa5a7
6 changed files with 178 additions and 69 deletions

View File

@@ -286,7 +286,6 @@ namespace OpenRA
Console.WriteLine("Loading mod: {0}", mod); Console.WriteLine("Loading mod: {0}", mod);
Settings.Game.Mod = mod; Settings.Game.Mod = mod;
Sound.StopMusic();
Sound.StopVideo(); Sound.StopVideo();
Sound.Initialize(); Sound.Initialize();

View File

@@ -182,6 +182,7 @@
<Compile Include="Traits\Util.cs" /> <Compile Include="Traits\Util.cs" />
<Compile Include="Traits\ValidateOrder.cs" /> <Compile Include="Traits\ValidateOrder.cs" />
<Compile Include="Traits\World\Country.cs" /> <Compile Include="Traits\World\Country.cs" />
<Compile Include="Traits\World\MusicPlaylist.cs" />
<Compile Include="Traits\World\ResourceType.cs" /> <Compile Include="Traits\World\ResourceType.cs" />
<Compile Include="Traits\World\ScreenShaker.cs" /> <Compile Include="Traits\World\ScreenShaker.cs" />
<Compile Include="Traits\World\Shroud.cs" /> <Compile Include="Traits\World\Shroud.cs" />

View File

@@ -0,0 +1,150 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Linq;
using OpenRA.GameRules;
namespace OpenRA.Traits
{
[Desc("Trait for music handling. Attach this to the world actor.")]
public class MusicPlaylistInfo : ITraitInfo
{
public readonly string StartingMusic = null;
public readonly bool LoopStartingMusic = false;
public object Create(ActorInitializer init) { return new MusicPlaylist(init.World, this); }
}
public class MusicPlaylist : INotifyActorDisposing
{
readonly MusicInfo[] random;
readonly MusicInfo[] playlist;
public readonly bool IsMusicAvailable;
MusicInfo currentSong;
bool repeat;
public MusicPlaylist(World world, MusicPlaylistInfo info)
{
IsMusicAvailable = world.Map.Rules.InstalledMusic.Any();
playlist = world.Map.Rules.InstalledMusic.Select(a => a.Value).ToArray();
if (!IsMusicAvailable)
return;
random = playlist.Shuffle(Game.CosmeticRandom).ToArray();
if (Game.Settings.Sound.MapMusic
&& !string.IsNullOrEmpty(info.StartingMusic)
&& world.Map.Rules.Music.ContainsKey(info.StartingMusic)
&& world.Map.Rules.Music[info.StartingMusic].Exists)
{
currentSong = world.Map.Rules.Music[info.StartingMusic];
repeat = info.LoopStartingMusic;
}
else
{
currentSong = Game.Settings.Sound.Shuffle ? random.First() : playlist.First();
repeat = Game.Settings.Sound.Repeat;
}
Play();
}
public MusicInfo CurrentSong()
{
return currentSong;
}
public MusicInfo[] AvailablePlaylist()
{
// TO-DO: add filter options for Race-specific music
return playlist;
}
void Play()
{
if (currentSong == null || !IsMusicAvailable)
return;
Sound.PlayMusicThen(currentSong, () =>
{
if (!repeat)
currentSong = GetNextSong();
Play();
});
}
public void Play(MusicInfo music)
{
if (music == null || !IsMusicAvailable)
return;
currentSong = music;
repeat = Game.Settings.Sound.Repeat;
Sound.PlayMusicThen(music, () =>
{
if (!repeat)
currentSong = GetNextSong();
Play();
});
}
public void Play(MusicInfo music, Action onComplete)
{
if (music == null || !IsMusicAvailable)
return;
currentSong = music;
Sound.PlayMusicThen(music, onComplete);
}
public MusicInfo GetNextSong()
{
return GetSong(false);
}
public MusicInfo GetPrevSong()
{
return GetSong(true);
}
MusicInfo GetSong(bool reverse)
{
if (!IsMusicAvailable)
return null;
var songs = Game.Settings.Sound.Shuffle ? random : playlist;
return reverse ? songs.SkipWhile(m => m != currentSong)
.Skip(1).FirstOrDefault() ?? songs.FirstOrDefault() :
songs.Reverse().SkipWhile(m => m != currentSong)
.Skip(1).FirstOrDefault() ?? songs.Reverse().FirstOrDefault();
}
public void Stop()
{
currentSong = null;
Sound.StopMusic();
}
public void Disposing(Actor self)
{
if (currentSong != null)
Stop();
}
}
}

View File

@@ -388,7 +388,6 @@ namespace OpenRA
frameEndActions.Clear(); frameEndActions.Clear();
Sound.StopAudio(); Sound.StopAudio();
Sound.StopMusic();
Sound.StopVideo(); Sound.StopVideo();
// Dispose newer actors first, and the world actor last // Dispose newer actors first, and the world actor last

View File

@@ -11,26 +11,28 @@
using System; using System;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Linq;
using Eluant; using Eluant;
using OpenRA.Effects; using OpenRA.Effects;
using OpenRA.FileFormats; using OpenRA.FileFormats;
using OpenRA.FileSystem; using OpenRA.FileSystem;
using OpenRA.GameRules;
using OpenRA.Graphics; using OpenRA.Graphics;
using OpenRA.Mods.Common.Effects; using OpenRA.Mods.Common.Effects;
using OpenRA.Scripting; using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting namespace OpenRA.Mods.Common.Scripting
{ {
[ScriptGlobal("Media")] [ScriptGlobal("Media")]
public class MediaGlobal : ScriptGlobal public class MediaGlobal : ScriptGlobal
{ {
World world; readonly World world;
readonly MusicPlaylist playlist;
public MediaGlobal(ScriptContext context) public MediaGlobal(ScriptContext context)
: base(context) : base(context)
{ {
world = context.World; world = context.World;
playlist = world.WorldActor.Trait<MusicPlaylist>();
} }
[Desc("Play an announcer voice listed in notifications.yaml")] [Desc("Play an announcer voice listed in notifications.yaml")]
@@ -51,23 +53,15 @@ namespace OpenRA.Mods.Common.Scripting
Sound.Play(file); Sound.Play(file);
} }
MusicInfo previousMusic;
Action onComplete; Action onComplete;
[Desc("Play track defined in music.yaml or keep it empty for a random song.")] [Desc("Play track defined in music.yaml or keep it empty for a random song.")]
public void PlayMusic(string track = null, LuaFunction func = null) public void PlayMusic(string track = null, LuaFunction func = null)
{ {
if (!Game.Settings.Sound.MapMusic) if (!Game.Settings.Sound.MapMusic || !playlist.IsMusicAvailable)
return;
var music = world.Map.Rules.InstalledMusic.Select(a => a.Value).ToArray();
if (!music.Any())
return; return;
var musicInfo = !string.IsNullOrEmpty(track) ? world.Map.Rules.Music[track] var musicInfo = !string.IsNullOrEmpty(track) ? world.Map.Rules.Music[track]
: Game.Settings.Sound.Repeat && previousMusic != null ? previousMusic : playlist.GetNextSong();
: Game.Settings.Sound.Shuffle ? music.Random(Game.CosmeticRandom)
: previousMusic == null ? music.First()
: music.SkipWhile(s => s != previousMusic).Skip(1).First();
if (func != null) if (func != null)
{ {
@@ -84,19 +78,17 @@ namespace OpenRA.Mods.Common.Scripting
Context.FatalError(e.Message); Context.FatalError(e.Message);
} }
}; };
playlist.Play(musicInfo, onComplete);
} }
else else
onComplete = () => { }; playlist.Play(musicInfo);
Sound.PlayMusicThen(musicInfo, onComplete);
previousMusic = Sound.CurrentMusic;
} }
[Desc("Stop the current song.")] [Desc("Stop the current song.")]
public void StopMusic() public void StopMusic()
{ {
Sound.StopMusic(); playlist.Stop();
} }
Action onCompleteFullscreen; Action onCompleteFullscreen;

View File

@@ -18,29 +18,24 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{ {
public class MusicPlayerLogic public class MusicPlayerLogic
{ {
readonly Ruleset modRules; readonly ScrollPanelWidget musicList;
readonly ScrollItemWidget itemTemplate;
bool installed; readonly MusicPlaylist musicPlaylist;
MusicInfo currentSong = null; MusicInfo currentSong = null;
MusicInfo[] music;
MusicInfo[] random;
ScrollPanelWidget musicList;
ScrollItemWidget itemTemplate;
[ObjectCreator.UseCtor] [ObjectCreator.UseCtor]
public MusicPlayerLogic(Widget widget, Ruleset modRules, World world, Action onExit) public MusicPlayerLogic(Widget widget, Ruleset modRules, World world, Action onExit)
{ {
this.modRules = modRules;
var panel = widget.Get("MUSIC_PANEL"); var panel = widget.Get("MUSIC_PANEL");
musicList = panel.Get<ScrollPanelWidget>("MUSIC_LIST"); musicList = panel.Get<ScrollPanelWidget>("MUSIC_LIST");
itemTemplate = musicList.Get<ScrollItemWidget>("MUSIC_TEMPLATE"); itemTemplate = musicList.Get<ScrollItemWidget>("MUSIC_TEMPLATE");
musicPlaylist = world.WorldActor.Trait<MusicPlaylist>();
BuildMusicTable(); BuildMusicTable();
Func<bool> noMusic = () => !installed; Func<bool> noMusic = () => !musicPlaylist.IsMusicAvailable;
panel.Get("NO_MUSIC_LABEL").IsVisible = noMusic; panel.Get("NO_MUSIC_LABEL").IsVisible = noMusic;
var playButton = panel.Get<ButtonWidget>("BUTTON_PLAY"); var playButton = panel.Get<ButtonWidget>("BUTTON_PLAY");
@@ -54,15 +49,15 @@ namespace OpenRA.Mods.Common.Widgets.Logic
pauseButton.IsVisible = () => Sound.MusicPlaying; pauseButton.IsVisible = () => Sound.MusicPlaying;
var stopButton = panel.Get<ButtonWidget>("BUTTON_STOP"); var stopButton = panel.Get<ButtonWidget>("BUTTON_STOP");
stopButton.OnClick = Sound.StopMusic; stopButton.OnClick = () => { musicPlaylist.Stop(); };
stopButton.IsDisabled = noMusic; stopButton.IsDisabled = noMusic;
var nextButton = panel.Get<ButtonWidget>("BUTTON_NEXT"); var nextButton = panel.Get<ButtonWidget>("BUTTON_NEXT");
nextButton.OnClick = () => { currentSong = GetNextSong(); Play(); }; nextButton.OnClick = () => { currentSong = musicPlaylist.GetNextSong(); Play(); };
nextButton.IsDisabled = noMusic; nextButton.IsDisabled = noMusic;
var prevButton = panel.Get<ButtonWidget>("BUTTON_PREV"); var prevButton = panel.Get<ButtonWidget>("BUTTON_PREV");
prevButton.OnClick = () => { currentSong = GetPrevSong(); Play(); }; prevButton.OnClick = () => { currentSong = musicPlaylist.GetPrevSong(); Play(); };
prevButton.IsDisabled = noMusic; prevButton.IsDisabled = noMusic;
var shuffleCheckbox = panel.Get<CheckboxWidget>("SHUFFLE"); var shuffleCheckbox = panel.Get<CheckboxWidget>("SHUFFLE");
@@ -111,11 +106,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic
public void BuildMusicTable() public void BuildMusicTable()
{ {
music = modRules.InstalledMusic.Select(a => a.Value).ToArray(); if (!musicPlaylist.IsMusicAvailable)
random = music.Shuffle(Game.CosmeticRandom).ToArray(); return;
currentSong = Sound.CurrentMusic;
var music = musicPlaylist.AvailablePlaylist();
currentSong = musicPlaylist.CurrentSong();
if (currentSong == null && music.Any()) if (currentSong == null && music.Any())
currentSong = Game.Settings.Sound.Shuffle ? random.First() : music.First(); currentSong = musicPlaylist.GetNextSong();
musicList.RemoveChildren(); musicList.RemoveChildren();
foreach (var s in music) foreach (var s in music)
@@ -124,8 +121,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
if (currentSong == null) if (currentSong == null)
currentSong = song; currentSong = song;
// TODO: We leak the currentSong MusicInfo across map load, so compare the Filename instead. var item = ScrollItemWidget.Setup(song.Filename, itemTemplate, () => currentSong == song, () => { currentSong = song; Play(); }, () => { });
var item = ScrollItemWidget.Setup(song.Filename, itemTemplate, () => currentSong.Filename == song.Filename, () => { currentSong = song; Play(); }, () => { });
item.Get<LabelWidget>("TITLE").GetText = () => song.Title; item.Get<LabelWidget>("TITLE").GetText = () => song.Title;
item.Get<LabelWidget>("LENGTH").GetText = () => SongLengthLabel(song); item.Get<LabelWidget>("LENGTH").GetText = () => SongLengthLabel(song);
musicList.AddChild(item); musicList.AddChild(item);
@@ -133,8 +129,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic
if (currentSong != null) if (currentSong != null)
musicList.ScrollToItem(currentSong.Filename); musicList.ScrollToItem(currentSong.Filename);
installed = modRules.InstalledMusic.Any();
} }
void Play() void Play()
@@ -143,38 +137,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic
return; return;
musicList.ScrollToItem(currentSong.Filename); musicList.ScrollToItem(currentSong.Filename);
musicPlaylist.Play(currentSong);
Sound.PlayMusicThen(currentSong, () =>
{
if (!Game.Settings.Sound.Repeat)
currentSong = GetNextSong();
Play();
});
} }
static string SongLengthLabel(MusicInfo song) static string SongLengthLabel(MusicInfo song)
{ {
return "{0:D1}:{1:D2}".F(song.Length / 60, song.Length % 60); return "{0:D1}:{1:D2}".F(song.Length / 60, song.Length % 60);
} }
MusicInfo GetNextSong()
{
if (!music.Any())
return null;
var songs = Game.Settings.Sound.Shuffle ? random : music;
return songs.SkipWhile(m => m != currentSong)
.Skip(1).FirstOrDefault() ?? songs.FirstOrDefault();
}
MusicInfo GetPrevSong()
{
if (!music.Any())
return null;
var songs = Game.Settings.Sound.Shuffle ? random : music;
return songs.Reverse().SkipWhile(m => m != currentSong)
.Skip(1).FirstOrDefault() ?? songs.Reverse().FirstOrDefault();
}
} }
} }