diff --git a/Action/Command.cs b/Action/Command.cs
index 31a0b3ba..893ba6de 100644
--- a/Action/Command.cs
+++ b/Action/Command.cs
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2022 Xibo Signage Ltd
+ * Copyright (C) 2023 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
@@ -21,7 +21,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Text.RegularExpressions;
using System.Windows;
+using System.Windows.Shapes;
namespace XiboClient.Action
{
@@ -52,6 +54,36 @@ public bool IsValidationRequired()
return !string.IsNullOrEmpty(Validation);
}
+ ///
+ ///
+ ///
+ ///
+ ///
+ private bool IsValid(string value)
+ {
+ LogMessage.Audit("Command", "IsValid", "Testing if " + Code + " is valid, output to test is [" + value + "]");
+
+ // Do we need to validate?
+ if (IsValidationRequired())
+ {
+ // Is the validation string a regex.
+ try
+ {
+ Match match = Regex.Match(value, Validation);
+ return match.Success;
+ }
+ catch
+ {
+ // Fallback to a string comparison
+ return value.Contains(Validation);
+ }
+ }
+ else
+ {
+ return true;
+ }
+ }
+
///
/// Run the Command
///
@@ -67,14 +99,7 @@ public bool Run()
Rs232Command rs232 = new Rs232Command(this);
string line = rs232.Run();
- if (IsValidationRequired())
- {
- return line == Validation;
- }
- else
- {
- return true;
- }
+ return IsValid(line);
}
else if (CommandString == "SoftRestart")
{
@@ -90,14 +115,7 @@ public bool Run()
HttpCommand command = new HttpCommand(this);
var httpStatus = command.RunAsync();
- if (IsValidationRequired())
- {
- return httpStatus.Result + "" == Validation;
- }
- else
- {
- return true;
- }
+ return IsValid(httpStatus.Result + "");
}
else
{
@@ -111,29 +129,32 @@ public bool Run()
startInfo.FileName = "cmd.exe";
startInfo.Arguments = "/C " + CommandString;
startInfo.UseShellExecute = false;
-
- if (IsValidationRequired())
- startInfo.RedirectStandardOutput = true;
+ startInfo.RedirectStandardOutput = true;
process.StartInfo = startInfo;
process.Start();
+ process.Exited += Process_Exited;
- if (IsValidationRequired())
+ string line = "";
+ while (!process.StandardOutput.EndOfStream)
{
- string line = "";
- while (!process.StandardOutput.EndOfStream)
- {
- line += process.StandardOutput.ReadLine();
- }
-
- return line == Validation;
+ line += process.StandardOutput.ReadLine();
}
- else
- return true;
+
+ return IsValid(line);
}
}
}
+ private void Process_Exited(object sender, EventArgs e)
+ {
+ int exitCode = ((Process)sender).ExitCode;
+ if (exitCode != 0)
+ {
+ LogMessage.Audit("Command", "Run", "Non-zero exit code [" + exitCode + "] returned for command " + Code);
+ }
+ }
+
///
/// Get a command from Application Settings based on its Command Code
///
diff --git a/Adspace/ExchangeManager.cs b/Adspace/ExchangeManager.cs
index a2778eda..dc82e8b5 100644
--- a/Adspace/ExchangeManager.cs
+++ b/Adspace/ExchangeManager.cs
@@ -672,87 +672,96 @@ private List Request(Url url, Ad wrappedAd)
XmlNode inlineNode = adNode.SelectSingleNode("./InLine");
if (inlineNode != null)
{
- // Title
- XmlNode titleNode = inlineNode.SelectSingleNode("./AdTitle");
- if (titleNode != null)
- {
- ad.Title = titleNode.InnerText.Trim();
- }
-
- // Get and impression/error URLs included with this wrap
- XmlNode errorUrlNode = inlineNode.SelectSingleNode("./Error");
- if (errorUrlNode != null)
+ try
{
- string errorUrl = errorUrlNode.InnerText.Trim();
- if (errorUrl != "about:blank")
+ // Title
+ XmlNode titleNode = inlineNode.SelectSingleNode("./AdTitle");
+ if (titleNode != null)
{
- ad.ErrorUrls.Add(errorUrl + ad.WrapperExtendUrl);
+ ad.Title = titleNode.InnerText.Trim();
}
- }
- XmlNode impressionUrlNode = inlineNode.SelectSingleNode("./Impression");
- if (impressionUrlNode != null)
- {
- string impressionUrl = impressionUrlNode.InnerText.Trim();
- if (impressionUrl != "about:blank")
+ // Get and impression/error URLs included with this wrap
+ XmlNode errorUrlNode = inlineNode.SelectSingleNode("./Error");
+ if (errorUrlNode != null)
{
- ad.ImpressionUrls.Add(impressionUrl + ad.WrapperExtendUrl);
+ string errorUrl = errorUrlNode.InnerText.Trim();
+ if (errorUrl != "about:blank")
+ {
+ ad.ErrorUrls.Add(errorUrl + ad.WrapperExtendUrl);
+ }
}
- }
- // Creatives
- XmlNode creativeNode = inlineNode.SelectSingleNode("./Creatives/Creative");
- if (creativeNode != null)
- {
- ad.CreativeId = creativeNode.Attributes["id"].Value;
+ XmlNode impressionUrlNode = inlineNode.SelectSingleNode("./Impression");
+ if (impressionUrlNode != null)
+ {
+ string impressionUrl = impressionUrlNode.InnerText.Trim();
+ if (impressionUrl != "about:blank")
+ {
+ ad.ImpressionUrls.Add(impressionUrl + ad.WrapperExtendUrl);
+ }
+ }
- // Get the duration.
- XmlNode creativeDurationNode = creativeNode.SelectSingleNode("./Linear/Duration");
- if (creativeDurationNode != null)
+ // Creatives
+ XmlNode creativeNode = inlineNode.SelectSingleNode("./Creatives/Creative");
+ if (creativeNode != null)
{
- ad.Duration = creativeDurationNode.InnerText.Trim();
+ ad.CreativeId = creativeNode.Attributes["id"].Value;
+
+ // Get the duration.
+ XmlNode creativeDurationNode = creativeNode.SelectSingleNode("./Linear/Duration");
+ if (creativeDurationNode != null)
+ {
+ ad.Duration = creativeDurationNode.InnerText.Trim();
+ }
+ else
+ {
+ ReportError(ad.ErrorUrls, 302);
+ continue;
+ }
+
+ // Get the media file
+ XmlNode creativeMediaNode = creativeNode.SelectSingleNode("./Linear/MediaFiles/MediaFile");
+ if (creativeMediaNode != null)
+ {
+ ad.Url = creativeMediaNode.InnerText.Trim();
+ ad.Width = int.Parse(creativeMediaNode.Attributes["width"].Value);
+ ad.Height = int.Parse(creativeMediaNode.Attributes["height"].Value);
+ ad.Type = creativeMediaNode.Attributes["type"].Value;
+ }
+ else
+ {
+ ReportError(ad.ErrorUrls, 302);
+ continue;
+ }
}
else
{
- ReportError(ad.ErrorUrls, 302);
+ // Malformed Ad.
+ ReportError(ad.ErrorUrls, 300);
continue;
}
- // Get the media file
- XmlNode creativeMediaNode = creativeNode.SelectSingleNode("./Linear/MediaFiles/MediaFile");
- if (creativeMediaNode != null)
+ // Extensions
+ XmlNodeList extensionNodes = inlineNode.SelectNodes("./Extension");
+ foreach (XmlNode extensionNode in extensionNodes)
{
- ad.Url = creativeMediaNode.InnerText.Trim();
- ad.Width = int.Parse(creativeMediaNode.Attributes["width"].Value);
- ad.Height = int.Parse(creativeMediaNode.Attributes["height"].Value);
- ad.Type = creativeMediaNode.Attributes["type"].Value;
- }
- else
- {
- ReportError(ad.ErrorUrls, 302);
- continue;
+ switch (extensionNode.Attributes["type"].Value)
+ {
+ case "geoFence":
+ ad.IsGeoAware = true;
+ ad.GeoLocation = extensionNode.InnerText;
+ break;
+ }
}
}
- else
+ catch (Exception ex)
{
- // Malformed Ad.
+ LogMessage.Audit("ExchangeManager", "Request", "Error parsing response XML. e: " + ex.Message);
ReportError(ad.ErrorUrls, 300);
continue;
}
- // Extensions
- XmlNodeList extensionNodes = inlineNode.SelectNodes("./Extension");
- foreach (XmlNode extensionNode in extensionNodes)
- {
- switch (extensionNode.Attributes["type"].Value)
- {
- case "geoFence":
- ad.IsGeoAware = true;
- ad.GeoLocation = extensionNode.InnerText;
- break;
- }
- }
-
// Did this resolve from a wrapper? if so do some extra checks.
if (ad.IsWrapper)
{
diff --git a/App.xaml.cs b/App.xaml.cs
index 0af5758e..f3efb3d4 100644
--- a/App.xaml.cs
+++ b/App.xaml.cs
@@ -36,6 +36,7 @@ protected override void OnStartup(StartupEventArgs e)
// Add the Xibo Tracelistener
Trace.Listeners.Add(new XiboTraceListener());
+ bool shouldQuit = false;
try
{
// Check for any passed arguments
@@ -43,6 +44,7 @@ protected override void OnStartup(StartupEventArgs e)
{
if (e.Args[0].ToString() == "o")
{
+ shouldQuit = true;
RunSettings();
}
else
@@ -79,7 +81,7 @@ protected override void OnStartup(StartupEventArgs e)
}
catch (Exception ex)
{
- HandleUnhandledException(ex, "Startup", false);
+ HandleUnhandledException(ex, "Startup", shouldQuit);
}
// Always flush at the end
@@ -174,7 +176,7 @@ static void HandleUnhandledException(Object o, string source, bool quit)
// Complete failure, show something to the user in these circumstances.
if (quit)
{
- MessageBox.Show(ex.Message);
+ MessageBox.Show("Unhandled Exception: " + ex.Message + ". Stack Trace: " + e.StackTrace, "Fatal Error");
}
}
diff --git a/Logic/ApplicationSettings.cs b/Logic/ApplicationSettings.cs
index c282731f..58815c43 100644
--- a/Logic/ApplicationSettings.cs
+++ b/Logic/ApplicationSettings.cs
@@ -52,9 +52,9 @@ private static readonly Lazy
///
private List ExcludedProperties;
- public string ClientVersion { get; } = "3 R308.0";
+ public string ClientVersion { get; } = "3 R309.1";
public string Version { get; } = "6";
- public int ClientCodeVersion { get; } = 308;
+ public int ClientCodeVersion { get; } = 309;
private ApplicationSettings()
{
@@ -571,6 +571,11 @@ public bool InDownloadWindow
private int _maxLogFileUploads;
public int MaxLogFileUploads { get { return ((_maxLogFileUploads == 0) ? 10 : _maxLogFileUploads); } set { _maxLogFileUploads = value; } }
+ ///
+ /// The amount of time in seconds we wait before a video is considered timed out on load
+ ///
+ public int VideoStartTimeout { get; set; }
+
public bool PowerpointEnabled { get; set; }
public bool StatsEnabled { get; set; }
public bool ExpireModifiedLayouts { get; set; }
diff --git a/Logic/ScheduleManager.cs b/Logic/ScheduleManager.cs
index 91018df9..1318deda 100644
--- a/Logic/ScheduleManager.cs
+++ b/Logic/ScheduleManager.cs
@@ -438,13 +438,14 @@ private bool IsNewScheduleAvailable()
List overlaySchedule = LoadNewOverlaySchedule();
// Load any adspace exchange schedules
+ ScheduleItem axeScheduleItem = null;
if (ApplicationSettings.Default.IsAdspaceEnabled)
{
exchangeManager.SetActive(true);
exchangeManager.Configure();
if (exchangeManager.ShareOfVoice > 0)
{
- parsedSchedule.Add(ScheduleItem.CreateForAdspaceExchange(exchangeManager.AverageAdDuration, exchangeManager.ShareOfVoice));
+ axeScheduleItem = ScheduleItem.CreateForAdspaceExchange(exchangeManager.AverageAdDuration, exchangeManager.ShareOfVoice);
}
}
else
@@ -457,7 +458,7 @@ private bool IsNewScheduleAvailable()
if (newSchedule.Count <= 0)
{
// No overrides, so we parse in our normal/interrupt layout mix.
- newSchedule = ResolveNormalAndInterrupts(ParseCyclePlayback(parsedSchedule));
+ newSchedule = ResolveNormalAndInterrupts(ParseCyclePlayback(parsedSchedule), axeScheduleItem);
}
// If we have come out of this process without any schedule, then we ought to assign the default
@@ -755,7 +756,7 @@ private List GetHighestPriority(List schedule)
///
///
///
- private List ResolveNormalAndInterrupts(List schedule)
+ private List ResolveNormalAndInterrupts(List schedule, ScheduleItem axeScheduleItem)
{
// Clear any currently set durations
foreach (ScheduleItem item in schedule)
@@ -767,9 +768,24 @@ private List ResolveNormalAndInterrupts(List schedul
List normal = GetNormalSchedule(schedule);
List interrupt = GetInterruptSchedule(schedule);
- if (interrupt.Count <= 0)
+ // How much time should we give to AXE
+ if (axeScheduleItem != null)
{
- return normal;
+ int totalInterruptSov = 0;
+ foreach (ScheduleItem item in interrupt)
+ {
+ totalInterruptSov += item.ShareOfVoice;
+
+ LogMessage.Audit("ScheduleManager", "ResolveNormalAndInterrupts", "Reducing AXE SOV by total interrupt SOV: " + totalInterruptSov);
+
+ axeScheduleItem.ShareOfVoice -= totalInterruptSov;
+
+ // If we still have some, then add that into our interrupt schedule
+ if (axeScheduleItem.ShareOfVoice > 0)
+ {
+ interrupt.Add(axeScheduleItem);
+ }
+ }
}
// If we have an empty normal schedule, pop the default in there
@@ -781,6 +797,12 @@ private List ResolveNormalAndInterrupts(List schedul
};
}
+ // If we have an empty interrupt schedule, just return the normal schedule.
+ if (interrupt.Count <= 0)
+ {
+ return normal;
+ }
+
// We do have interrupts
// organise the schedule loop so that our interrupts play according to their share of voice requirements.
List resolved = new List();
diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs
index 5014151f..9a9d0cab 100644
--- a/MainWindow.xaml.cs
+++ b/MainWindow.xaml.cs
@@ -362,10 +362,11 @@ private void MainWindow_Loaded(object sender, RoutedEventArgs e)
RootCachePath = ApplicationSettings.Default.LibraryPath + @"\CEF",
CachePath = ApplicationSettings.Default.LibraryPath + @"\CEF",
LogFile = ApplicationSettings.Default.LibraryPath + @"\CEF\cef.log",
- LogSeverity = CefSharp.LogSeverity.Fatal
+ LogSeverity = CefSharp.LogSeverity.Fatal,
};
settings.CefCommandLineArgs["autoplay-policy"] = "no-user-gesture-required";
settings.CefCommandLineArgs["disable-pinch"] = "1";
+ settings.CefCommandLineArgs["disable-usb-keyboard-detect"] = "1";
CefSharp.Cef.Initialize(settings);
}
@@ -995,9 +996,12 @@ public void ManageOverlays(List overlays)
}
// Add overlays
- foreach (Layout overlay in _overlays)
+ if (_overlays != null)
{
- actions.AddRange(overlay.GetActions());
+ foreach (Layout overlay in _overlays)
+ {
+ actions.AddRange(overlay.GetActions());
+ }
}
// Add the current schedule actions
@@ -1107,7 +1111,7 @@ public void HandleActionTrigger(string triggerType, string triggerCode, int sour
}
// Match the sourceId if it has been provided
- if (sourceId != 0 && sourceId == action.SourceId)
+ if (sourceId != 0 && sourceId != action.SourceId)
{
continue;
}
diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs
index 743ef70e..c6547374 100644
--- a/Properties/AssemblyInfo.cs
+++ b/Properties/AssemblyInfo.cs
@@ -10,7 +10,7 @@
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Xibo Digital Signage")]
[assembly: AssemblyProduct("Xibo")]
-[assembly: AssemblyCopyright("Copyright © Xibo Signage Ltd 2022")]
+[assembly: AssemblyCopyright("Copyright © Xibo Signage Ltd 2023")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -49,6 +49,6 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("3.308.0.0")]
-[assembly: AssemblyFileVersion("3.308.0.0")]
+[assembly: AssemblyVersion("3.309.1.0")]
+[assembly: AssemblyFileVersion("3.309.1.0")]
[assembly: Guid("3bd467a4-4ef9-466a-b156-a79c13a863f7")]
diff --git a/Rendering/Video.cs b/Rendering/Video.cs
index b3a4af60..3caa5af2 100644
--- a/Rendering/Video.cs
+++ b/Rendering/Video.cs
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2022 Xibo Signage Ltd
+ * Copyright (C) 2023 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
@@ -36,6 +36,7 @@ class Video : Media
private bool isLooping = false;
private readonly bool isFullScreenRequest = false;
private bool _openCalled = false;
+ private bool _stopped = false;
///
/// Should this be visible? Audio sets this to false.
@@ -158,19 +159,19 @@ private void MediaElement_Loaded(object sender, RoutedEventArgs e)
}
// We make a watchman to check that the video actually gets loaded.
- var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(4) };
+ var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(ApplicationSettings.Default.VideoStartTimeout) };
timer.Tick += (timerSender, args) =>
{
// You only tick once
timer.Stop();
// Check to see if open has been called.
- if (!_openCalled)
+ if (!_openCalled && !IsFailedToPlay && !_stopped)
{
- Trace.WriteLine(new LogMessage("Video", "MediaElement_Loaded: " + this.Id + " Open not called after 4 seconds, marking unsafe and Expiring."), LogType.Error.ToString());
+ LogMessage.Error("Video", "MediaElement_Loaded", this.Id + " Open not called after " + ApplicationSettings.Default.VideoStartTimeout + " seconds, marking unsafe and Expiring.");
// Add this to a temporary blacklist so that we don't repeat it too quickly
- CacheManager.Instance.AddUnsafeItem(UnsafeItemType.Media, UnsafeFaultCodes.VideoUnexpected, LayoutId, Id, "Video Failed: Open not called after 4 seconds", 120);
+ CacheManager.Instance.AddUnsafeItem(UnsafeItemType.Media, UnsafeFaultCodes.VideoUnexpected, LayoutId, Id, "Video Failed: Open not called after " + ApplicationSettings.Default.VideoStartTimeout + " seconds", 120);
// Expire
SignalElapsedEvent();
@@ -307,6 +308,9 @@ public override void Stopped()
{
Trace.WriteLine(new LogMessage("Video", "Stopped: " + this.Id), LogType.Audit.ToString());
+ // We've stopped
+ _stopped = true;
+
// Remove the event handlers
this.mediaElement.MediaOpened -= MediaElement_MediaOpened;
this.mediaElement.Loaded -= MediaElement_Loaded;
diff --git a/Rendering/WebMedia.cs b/Rendering/WebMedia.cs
index e2fda399..19d521f7 100644
--- a/Rendering/WebMedia.cs
+++ b/Rendering/WebMedia.cs
@@ -18,6 +18,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see .
*/
+using EmbedIO.Utilities;
using Ionic.Zip;
using System;
using System.Diagnostics;
@@ -479,10 +480,16 @@ public static WebMedia GetConfiguredWebMedia(MediaOptions options)
{
// Decode the URL
string url = Uri.UnescapeDataString(options.uri);
-
- if (url.Contains(ApplicationSettings.Default.EdgeBrowserWhitelist))
+
+ // Split the white list by comma
+ string[] whiteList = ApplicationSettings.Default.EdgeBrowserWhitelist.Split(',');
+
+ foreach (string white in whiteList)
{
- return new WebEdge(options);
+ if (url.Contains(white))
+ {
+ return new WebEdge(options);
+ }
}
}
diff --git a/XiboClient.csproj b/XiboClient.csproj
index d7a20a39..196c1def 100644
--- a/XiboClient.csproj
+++ b/XiboClient.csproj
@@ -298,7 +298,7 @@
1.8.9
- 111.2.20
+ 114.2.100
1.2.0
@@ -322,22 +322,22 @@
0.3.6
- 7.0.4
+ 7.0.5
14.0.1016.290
- 1.0.1587.40
+ 1.0.1823.32
- 4.0.1.11
+ 4.0.1.12
13.0.3
- 3.1.6
+ 3.1.9
5.0.1
diff --git a/default.config.xml b/default.config.xml
index 436955c7..801f3e14 100644
--- a/default.config.xml
+++ b/default.config.xml
@@ -57,4 +57,5 @@
individual
false
false
+ 10
\ No newline at end of file