From 60fafaa5a7731090334f7d427b8f2b2c161053cb Mon Sep 17 00:00:00 2001 From: Zimmermann Gyula Date: Fri, 26 Jun 2015 02:45:43 +0200 Subject: [PATCH] Refactors the music player. --- OpenRA.Game/Game.cs | 1 - OpenRA.Game/OpenRA.Game.csproj | 1 + OpenRA.Game/Traits/World/MusicPlaylist.cs | 150 ++++++++++++++++++ OpenRA.Game/World.cs | 1 - .../Scripting/Global/MediaGlobal.cs | 30 ++-- .../Widgets/Logic/MusicPlayerLogic.cs | 64 ++------ 6 files changed, 178 insertions(+), 69 deletions(-) create mode 100644 OpenRA.Game/Traits/World/MusicPlaylist.cs diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index 9f3ef7729e..9088610862 100644 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -286,7 +286,6 @@ namespace OpenRA Console.WriteLine("Loading mod: {0}", mod); Settings.Game.Mod = mod; - Sound.StopMusic(); Sound.StopVideo(); Sound.Initialize(); diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj index 29f7099e1d..d7ea45367c 100644 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -182,6 +182,7 @@ + diff --git a/OpenRA.Game/Traits/World/MusicPlaylist.cs b/OpenRA.Game/Traits/World/MusicPlaylist.cs new file mode 100644 index 0000000000..49201338f0 --- /dev/null +++ b/OpenRA.Game/Traits/World/MusicPlaylist.cs @@ -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(); + } + } +} diff --git a/OpenRA.Game/World.cs b/OpenRA.Game/World.cs index 16ed7023d8..2413c73913 100644 --- a/OpenRA.Game/World.cs +++ b/OpenRA.Game/World.cs @@ -388,7 +388,6 @@ namespace OpenRA frameEndActions.Clear(); Sound.StopAudio(); - Sound.StopMusic(); Sound.StopVideo(); // Dispose newer actors first, and the world actor last diff --git a/OpenRA.Mods.Common/Scripting/Global/MediaGlobal.cs b/OpenRA.Mods.Common/Scripting/Global/MediaGlobal.cs index bcc0d41058..182a2ea815 100644 --- a/OpenRA.Mods.Common/Scripting/Global/MediaGlobal.cs +++ b/OpenRA.Mods.Common/Scripting/Global/MediaGlobal.cs @@ -11,26 +11,28 @@ using System; using System.Drawing; using System.IO; -using System.Linq; using Eluant; using OpenRA.Effects; using OpenRA.FileFormats; using OpenRA.FileSystem; -using OpenRA.GameRules; using OpenRA.Graphics; using OpenRA.Mods.Common.Effects; using OpenRA.Scripting; +using OpenRA.Traits; namespace OpenRA.Mods.Common.Scripting { [ScriptGlobal("Media")] public class MediaGlobal : ScriptGlobal { - World world; + readonly World world; + readonly MusicPlaylist playlist; + public MediaGlobal(ScriptContext context) : base(context) { world = context.World; + playlist = world.WorldActor.Trait(); } [Desc("Play an announcer voice listed in notifications.yaml")] @@ -51,23 +53,15 @@ namespace OpenRA.Mods.Common.Scripting Sound.Play(file); } - MusicInfo previousMusic; Action onComplete; [Desc("Play track defined in music.yaml or keep it empty for a random song.")] public void PlayMusic(string track = null, LuaFunction func = null) { - if (!Game.Settings.Sound.MapMusic) - return; - - var music = world.Map.Rules.InstalledMusic.Select(a => a.Value).ToArray(); - if (!music.Any()) + if (!Game.Settings.Sound.MapMusic || !playlist.IsMusicAvailable) return; var musicInfo = !string.IsNullOrEmpty(track) ? world.Map.Rules.Music[track] - : Game.Settings.Sound.Repeat && previousMusic != null ? previousMusic - : Game.Settings.Sound.Shuffle ? music.Random(Game.CosmeticRandom) - : previousMusic == null ? music.First() - : music.SkipWhile(s => s != previousMusic).Skip(1).First(); + : playlist.GetNextSong(); if (func != null) { @@ -84,19 +78,17 @@ namespace OpenRA.Mods.Common.Scripting Context.FatalError(e.Message); } }; + + playlist.Play(musicInfo, onComplete); } else - onComplete = () => { }; - - Sound.PlayMusicThen(musicInfo, onComplete); - - previousMusic = Sound.CurrentMusic; + playlist.Play(musicInfo); } [Desc("Stop the current song.")] public void StopMusic() { - Sound.StopMusic(); + playlist.Stop(); } Action onCompleteFullscreen; diff --git a/OpenRA.Mods.Common/Widgets/Logic/MusicPlayerLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/MusicPlayerLogic.cs index 67e2a37b39..2830512b0e 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/MusicPlayerLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/MusicPlayerLogic.cs @@ -18,29 +18,24 @@ namespace OpenRA.Mods.Common.Widgets.Logic { public class MusicPlayerLogic { - readonly Ruleset modRules; + readonly ScrollPanelWidget musicList; + readonly ScrollItemWidget itemTemplate; - bool installed; + readonly MusicPlaylist musicPlaylist; MusicInfo currentSong = null; - MusicInfo[] music; - MusicInfo[] random; - ScrollPanelWidget musicList; - - ScrollItemWidget itemTemplate; [ObjectCreator.UseCtor] public MusicPlayerLogic(Widget widget, Ruleset modRules, World world, Action onExit) { - this.modRules = modRules; - var panel = widget.Get("MUSIC_PANEL"); musicList = panel.Get("MUSIC_LIST"); itemTemplate = musicList.Get("MUSIC_TEMPLATE"); + musicPlaylist = world.WorldActor.Trait(); BuildMusicTable(); - Func noMusic = () => !installed; + Func noMusic = () => !musicPlaylist.IsMusicAvailable; panel.Get("NO_MUSIC_LABEL").IsVisible = noMusic; var playButton = panel.Get("BUTTON_PLAY"); @@ -54,15 +49,15 @@ namespace OpenRA.Mods.Common.Widgets.Logic pauseButton.IsVisible = () => Sound.MusicPlaying; var stopButton = panel.Get("BUTTON_STOP"); - stopButton.OnClick = Sound.StopMusic; + stopButton.OnClick = () => { musicPlaylist.Stop(); }; stopButton.IsDisabled = noMusic; var nextButton = panel.Get("BUTTON_NEXT"); - nextButton.OnClick = () => { currentSong = GetNextSong(); Play(); }; + nextButton.OnClick = () => { currentSong = musicPlaylist.GetNextSong(); Play(); }; nextButton.IsDisabled = noMusic; var prevButton = panel.Get("BUTTON_PREV"); - prevButton.OnClick = () => { currentSong = GetPrevSong(); Play(); }; + prevButton.OnClick = () => { currentSong = musicPlaylist.GetPrevSong(); Play(); }; prevButton.IsDisabled = noMusic; var shuffleCheckbox = panel.Get("SHUFFLE"); @@ -111,11 +106,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic public void BuildMusicTable() { - music = modRules.InstalledMusic.Select(a => a.Value).ToArray(); - random = music.Shuffle(Game.CosmeticRandom).ToArray(); - currentSong = Sound.CurrentMusic; + if (!musicPlaylist.IsMusicAvailable) + return; + + var music = musicPlaylist.AvailablePlaylist(); + currentSong = musicPlaylist.CurrentSong(); if (currentSong == null && music.Any()) - currentSong = Game.Settings.Sound.Shuffle ? random.First() : music.First(); + currentSong = musicPlaylist.GetNextSong(); musicList.RemoveChildren(); foreach (var s in music) @@ -124,8 +121,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic if (currentSong == null) currentSong = song; - // TODO: We leak the currentSong MusicInfo across map load, so compare the Filename instead. - var item = ScrollItemWidget.Setup(song.Filename, itemTemplate, () => currentSong.Filename == song.Filename, () => { currentSong = song; Play(); }, () => { }); + var item = ScrollItemWidget.Setup(song.Filename, itemTemplate, () => currentSong == song, () => { currentSong = song; Play(); }, () => { }); item.Get("TITLE").GetText = () => song.Title; item.Get("LENGTH").GetText = () => SongLengthLabel(song); musicList.AddChild(item); @@ -133,8 +129,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic if (currentSong != null) musicList.ScrollToItem(currentSong.Filename); - - installed = modRules.InstalledMusic.Any(); } void Play() @@ -143,38 +137,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic return; musicList.ScrollToItem(currentSong.Filename); - - Sound.PlayMusicThen(currentSong, () => - { - if (!Game.Settings.Sound.Repeat) - currentSong = GetNextSong(); - Play(); - }); + musicPlaylist.Play(currentSong); } static string SongLengthLabel(MusicInfo song) { 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(); - } } }