Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Video Splash Screen #3867

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions source/Playnite.FullscreenApp/FullscreenApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public FullscreenAppViewModel MainModel
}
}

private SplashScreen splashScreen;
private ExtendedSplashScreen splashScreen;
private bool sdlInitialized = false;
public static AudioEngine Audio { get; private set; }
public static IntPtr NavigateSound { get; private set; }
Expand All @@ -55,10 +55,10 @@ public FullscreenAppViewModel MainModel
get => PlayniteApplication.Current == null ? null : (FullscreenApplication)PlayniteApplication.Current;
}

public FullscreenApplication(Func<Application> appInitializer, SplashScreen splashScreen, CmdLineOptions cmdLine)
public FullscreenApplication(Func<Application> appInitializer, ExtendedSplashScreen splashScreen, CmdLineOptions cmdLine)
: base(appInitializer, ApplicationMode.Fullscreen, cmdLine)
{
this.splashScreen = splashScreen;
this.splashScreen = splashScreen;
}

public override void ConfigureViews()
Expand Down Expand Up @@ -161,11 +161,13 @@ private async void OpenMainViewAsync()
Extensions.LoadScripts(AppSettings.DisabledPlugins, CmdLine.SafeStartup, AppSettings.DevelExtenions.Where(a => a.Selected == true).Select(a => a.Item).ToList());
OnExtensionsLoaded();

splashScreen?.Close(new TimeSpan(0));
MainModel.OpenView();
CurrentNative.MainWindow = MainModel.Window.Window;
await MainModel.ProcessStartupLibUpdate();
CurrentNative.MainWindow.Activate();
splashScreen?.Close(new TimeSpan(0));

await MainModel.ProcessStartupLibUpdate();

// This is most likely safe place to consider application to be started properly
FileSystem.DeleteFile(PlaynitePaths.SafeStartupFlagFile);
}
Expand Down
2 changes: 1 addition & 1 deletion source/Playnite.FullscreenApp/HiddenStyles.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
</Style>

<Style x:Key="MainWindowStyle" TargetType="WindowBase">
<Setter Property="Background" Value="GhostWhite" />
<Setter Property="Background" Value="Black" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="WindowChrome.WindowChrome">
<Setter.Value>
Expand Down
6 changes: 6 additions & 0 deletions source/Playnite.FullscreenApp/Playnite.FullscreenApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,13 @@
<Compile Include="Windows\UpdateWindow.xaml.cs">
<DependentUpon>UpdateWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Windows\VideoSplashScreen.xaml.cs">
<DependentUpon>VideoSplashScreen.xaml</DependentUpon>
</Compile>
<Compile Include="Windows\WindowTools.cs" />
<Page Include="Windows\VideoSplashScreen.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<SplashScreen Include="SplashScreen.png" />
<Resource Include="..\..\references\Fonts\*.*">
<Link>Fonts\%(Filename)%(Extension)</Link>
Expand Down
5 changes: 3 additions & 2 deletions source/Playnite.FullscreenApp/ProgramEntry.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using CommandLine;
using Playnite.Common;
using Playnite.FullscreenApp.Windows;
using Playnite.SDK;
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -57,11 +58,11 @@ public static void Main(string[] args)
cmdLine = options.Value;
}

SplashScreen splash = null;
ExtendedSplashScreen splash = null;
var procCount = Process.GetProcesses().Where(a => a.ProcessName.StartsWith("Playnite.")).Count();
if (cmdLine.Start.IsNullOrEmpty() && !cmdLine.HideSplashScreen && procCount == 1)
{
splash = new SplashScreen("SplashScreen.png");
splash = new ExtendedSplashScreen("SplashScreen.png");
splash.Show(false);
}

Expand Down
23 changes: 23 additions & 0 deletions source/Playnite.FullscreenApp/Windows/VideoSplashScreen.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Window x:Class="Playnite.FullscreenApp.Windows.VideoSplash"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignWidth="1920" d:DesignHeight="1080"
mc:Ignorable="d"
ShowInTaskbar="False"
WindowStyle="None"
WindowStartupLocation="CenterScreen"
WindowState="Maximized"
AllowsTransparency="False" Background="Black">
<Viewbox>
<Grid Height="1080" Width="1920" Name="GridMain">
<MediaElement Name="SplashVideo"
LoadedBehavior="Play"
UnloadedBehavior="Stop"
MediaFailed="SplashVideo_MediaFailed"
MediaEnded="SplashVideo_MediaEnded"
Stretch="UniformToFill"/>
</Grid>
</Viewbox>
</Window>
199 changes: 199 additions & 0 deletions source/Playnite.FullscreenApp/Windows/VideoSplashScreen.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using Playnite.Common;

namespace Playnite.FullscreenApp.Windows
{
public class ThemeOptionsSettings
{ public const string ThemeOptionGuid = "904cbf3b-573f-48f8-9642-0a09d05c64ef";
public Dictionary<string, List<string>> SelectedPresets;
}

public partial class VideoSplash : Window
{
private DispatcherTimer _fadeTimer;
private double _volumeStep;
private double _opacityStep;

public VideoSplash(string videoSource)
{
InitializeComponent();
SplashVideo.Source = new Uri(videoSource, UriKind.RelativeOrAbsolute);
}

private void SplashVideo_MediaEnded(object sender, RoutedEventArgs e)
{
SplashVideo.LoadedBehavior = MediaState.Pause;
}

private void SplashVideo_MediaFailed(object sender, ExceptionRoutedEventArgs e)
{
CloseSplashScreen();
}

public void CloseSplashScreen()
{
StartFadeOut();
}

private void StartFadeOut()
{
_volumeStep = SplashVideo.Volume / 10.0; // 10 steps for 1 seconds
_opacityStep = this.Opacity / 10.0; // 10 steps for 1 seconds
_fadeTimer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(100) // 100 ms interval
};
_fadeTimer.Tick += FadeOut;
_fadeTimer.Start();
}

private void FadeOut(object sender, EventArgs e)
{
if (SplashVideo.Volume > 0)
{
SplashVideo.Volume -= _volumeStep;
}

if (this.Opacity > 0)
{
this.Opacity -= _opacityStep;
}

if (SplashVideo.Volume <= 0 && this.Opacity <= 0)
{
_fadeTimer.Stop();
SplashVideo.LoadedBehavior = MediaState.Close; // Stop the media element
Dispatcher.InvokeShutdown();
}
}
}

public class ExtendedSplashScreen
{
private Thread _splashThread;
private readonly string _videoSource;
private Dispatcher _splashDispatcher;
private VideoSplash _splash;
private SplashScreen _splashScreen;

public ExtendedSplashScreen(string splashImage)
{
_videoSource = ChooseVideo();
if ( _videoSource.IsNullOrEmpty())
{
_splashScreen = new SplashScreen(splashImage);
}
}
public void Show(bool hideSmooth)
{
if (_videoSource.IsNullOrEmpty())
{
_splashScreen?.Show(hideSmooth);
}
else
{
ShowVideoSplash();
}
}

public void Close(TimeSpan delay)
{
if (_videoSource.IsNullOrEmpty())
{
_splashScreen?.Close(delay);
}
else
{
CloseVideoSplash(delay);
}
}

private void ShowVideoSplash()
{
if (_splashThread != null)
{
return;
}

_splashThread = new Thread(() =>
{
_splashDispatcher = Dispatcher.CurrentDispatcher;
_splash = new VideoSplash(_videoSource);
_splash.Show();
Dispatcher.Run();
});

_splashThread.SetApartmentState(ApartmentState.STA);
_splashThread.IsBackground = true;
_splashThread.Start();
}

private void CloseVideoSplash(TimeSpan delay)
{
if (_videoSource.IsNullOrEmpty())
{
_splashScreen?.Close(delay);
}
else if (_splashThread != null)
{
Task.Delay(delay).ContinueWith(_ =>
{
_splashDispatcher?.Invoke(() =>
{
_splash.CloseSplashScreen();
});
_splashThread.Join();
_splashThread = null;
});
}
}
private string ChooseVideo()
{
// try to open settings and find fullscreen theme name
string settingsFile = PlaynitePaths.FullscreenConfigFilePath;
if (File.Exists(settingsFile))
{
FullscreenSettings settings = Serialization.FromJsonFile<FullscreenSettings>(settingsFile);
string currentTheme = settings.Theme;
ThemeManifest theme = ThemeManager.GetAvailableThemes(SDK.ApplicationMode.Fullscreen).FirstOrDefault(t => t.Id == currentTheme);

if ( theme?.DirectoryPath is string themeDir && Directory.Exists(themeDir) )
{
string video = Directory.GetFiles(themeDir, "SplashVideo.mp4", SearchOption.AllDirectories).FirstOrDefault();
if ( !String.IsNullOrEmpty(video) )
{
return video;
}

// Suport of ThemeOption extension
if ( File.Exists(Path.Combine(themeDir,"options.yaml")))
{
// theme is Option
string themeOptionsConfig = Path.Combine(PlaynitePaths.ExtensionsDataPath, ThemeOptionsSettings.ThemeOptionGuid, "config.json");
if (File.Exists(themeOptionsConfig))
{
ThemeOptionsSettings themeOptionSettings = Serialization.FromJsonFile<ThemeOptionsSettings>(themeOptionsConfig);
if (themeOptionSettings.SelectedPresets.TryGetValue(theme.Id, out List<string> presets))
{
var selectedPreset = presets.FirstOrDefault(s => s.ToLower().StartsWith("splashvideo."))??"splashVideo.default";
if (!selectedPreset.IsNullOrEmpty() && Directory.GetFiles(themeDir, selectedPreset + ".mp4", SearchOption.AllDirectories).FirstOrDefault() is string selectedVideo )
{
return selectedVideo;
}
}
}
}
}
}
return null;
}
}
}