diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..5d449a8a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,12 @@ +# Contributing +We'd love to work with you and accept your contributions. For small changes like bug fixes, typos, etc. Please Fork us and submit a pull request with the details of what you have changed. + +For larger changes, you will have to electronically sign a statement that indicates two things: + +* You are willingly licensing your contributions under the terms of the open source license of the project that you're contributing to. + +* You are legally able to license your contributions as stated. + +This is called a Contributor Licence Agreement or "CLA" for short. + +The standard licence for Xibo is the [AGPLv3](LICENSE). For more information please see [CONTRIBUTING](https://github.com/xibosignage/xibo/blob/master/CONTRIBUTING.md). \ No newline at end of file diff --git a/Control/Region.cs b/Control/Region.cs index 3d29a2b7..341764d4 100644 --- a/Control/Region.cs +++ b/Control/Region.cs @@ -1,6 +1,6 @@ /* * Xibo - Digitial Signage - http://www.xibo.org.uk - * Copyright (C) 2006-2014 Daniel Garner + * Copyright (C) 2006-2015 Daniel Garner * * This file is part of Xibo. * @@ -38,8 +38,8 @@ class Region : Panel private Media _media; private RegionOptions _options; - public bool _hasExpired = false; - public bool _layoutExpired = false; + private bool _hasExpired = false; + private bool _layoutExpired = false; private int _currentSequence = -1; // Stat objects @@ -101,6 +101,23 @@ public RegionOptions regionOptions } } + /// + /// Inform the region that the layout has expired + /// + public void setLayoutExpired() + { + _layoutExpired = true; + } + + /// + /// Has this region expired + /// + /// + public bool hasExpired() + { + return _hasExpired; + } + /// /// Evaulates the change in options /// @@ -343,6 +360,9 @@ private void ParseOptionsForMediaNode(XmlNode mediaNode, XmlAttributeCollection // There will be some stuff on option nodes XmlNode optionNode = mediaNode.FirstChild; + // Track if an update interval has been provided in the XLF + bool updateIntervalProvided = false; + // Loop through each option node foreach (XmlNode option in optionNode.ChildNodes) { @@ -371,6 +391,8 @@ private void ParseOptionsForMediaNode(XmlNode mediaNode, XmlAttributeCollection } else if (option.Name == "updateInterval") { + updateIntervalProvided = true; + try { _options.updateInterval = int.Parse(option.InnerText); @@ -410,6 +432,10 @@ private void ParseOptionsForMediaNode(XmlNode mediaNode, XmlAttributeCollection _options.javaScript = raw.InnerText; } } + + // Media Types without an update interval should be set to something rather high + if (!updateIntervalProvided) + _options.updateInterval = int.MaxValue; } /// @@ -508,10 +534,9 @@ private Media CreateNextMediaNode(RegionOptions options) /// private void StartMedia(Media media) { - media.RenderMedia(); - Trace.WriteLine(new LogMessage("Region - StartMedia", "Starting media"), LogType.Audit.ToString()); + media.RenderMedia(); Controls.Add(media); } @@ -536,8 +561,7 @@ private void StopMedia(Media media) } catch (Exception ex) { - Debug.WriteLine("No media to remove"); - Debug.WriteLine(ex.Message); + Trace.WriteLine(new LogMessage("Region - Stop Media", "Unable to dispose. Ex = " + ex.Message), LogType.Audit.ToString()); } } @@ -585,6 +609,10 @@ private void media_DurationElapsedEvent(int filesPlayed) // Increment the _current sequence by the number of filesPlayed (minus 1) _currentSequence = _currentSequence + (filesPlayed - 1); + // If this layout has been expired we know that everything will soon be torn down, so do nothing + if (_layoutExpired) + return; + // make some decisions about what to do next try { @@ -641,8 +669,7 @@ protected override void Dispose(bool disposing) } catch (Exception ex) { - Debug.WriteLine(ex.Message); - Debug.WriteLine("There was no media to dispose", "Region - Dispose"); + Trace.WriteLine(new LogMessage("Region - Dispose", "Unable to dispose media. Ex = " + ex.Message), LogType.Audit.ToString()); } finally { diff --git a/Log/ClientInfo.cs b/Log/ClientInfo.cs index 08fcc1d3..13f4b14b 100644 --- a/Log/ClientInfo.cs +++ b/Log/ClientInfo.cs @@ -162,6 +162,10 @@ public void AddToLogGrid(string message, LogType logType) return; } + // Prevent the log grid getting too large (clear at 500 messages) + if (logDataGridView.RowCount > 500) + logDataGridView.Rows.Clear(); + int newRow = logDataGridView.Rows.Add(); LogMessage logMessage; diff --git a/Log/LogMessage.cs b/Log/LogMessage.cs index 940234ec..a85b9a43 100644 --- a/Log/LogMessage.cs +++ b/Log/LogMessage.cs @@ -97,10 +97,10 @@ public override string ToString() // Just do this with a string builder rather than an XML builder. String theMessage; - theMessage = String.Format("{0}", SecurityElement.Escape(_message)); - theMessage += String.Format("{0}", _method); - theMessage += String.Format("{0}", LogDate); + theMessage = String.Format("{0}", LogDate); theMessage += String.Format("{0}", _thread); + theMessage += String.Format("{0}", _method); + theMessage += String.Format("{0}", SecurityElement.Escape(_message)); if (_scheduleId != 0) theMessage += String.Format("{0}", _scheduleId.ToString()); if (_layoutId != 0) theMessage += String.Format("{0}", _scheduleId.ToString()); diff --git a/Log/StatLog.cs b/Log/StatLog.cs index 17ac52dd..a56934e1 100644 --- a/Log/StatLog.cs +++ b/Log/StatLog.cs @@ -1,6 +1,6 @@ /* * Xibo - Digitial Signage - http://www.xibo.org.uk - * Copyright (C) 2009-2014 Spring Signage Ltd + * Copyright (C) 2009-2015 Spring Signage Ltd * * This file is part of Xibo. * @@ -32,6 +32,7 @@ namespace XiboClient { class StatLog { + public static object _locker = new object(); private Collection _stats; private HardwareKey _hardwareKey; @@ -194,58 +195,73 @@ private void FlushToFile() /// private void ProcessQueueToXmds() { - Debug.WriteLine(new LogMessage("FlushToXmds", String.Format("IN")), LogType.Audit.ToString()); - - // If we haven't had a successful connection recently, then don't log - if (ApplicationSettings.Default.XmdsLastConnection.AddSeconds((int)ApplicationSettings.Default.CollectInterval) < DateTime.Now) - return; - - // Get a list of all the log files waiting to be sent to XMDS. - string[] logFiles = Directory.GetFiles(ApplicationSettings.Default.LibraryPath, "*" + ApplicationSettings.Default.StatsLogFile + "*"); - - foreach (string fileName in logFiles) + try { - // If we have some, create an XMDS object - using (xmds.xmds logtoXmds = new xmds.xmds()) - { - logtoXmds.Url = ApplicationSettings.Default.XiboClient_xmds_xmds; - - // construct the log message - StringBuilder builder = new StringBuilder(); - builder.Append(""); - - foreach (string entry in File.ReadAllLines(fileName)) - builder.Append(entry); - - builder.Append(""); + // If we haven't had a successful connection recently, then don't log + if (ApplicationSettings.Default.XmdsLastConnection.AddSeconds((int)ApplicationSettings.Default.CollectInterval) < DateTime.Now) + return; - try - { - logtoXmds.SubmitStats(ApplicationSettings.Default.ServerKey, _hardwareKey.Key, builder.ToString()); - - // Delete the file we are on - File.Delete(fileName); - } - catch (WebException webEx) - { - // Increment the quantity of XMDS failures and bail out - ApplicationSettings.Default.IncrementXmdsErrorCount(); + lock (_locker) + { + // Get a list of all the log files waiting to be sent to XMDS. + string[] logFiles = Directory.GetFiles(ApplicationSettings.Default.LibraryPath, "*" + ApplicationSettings.Default.StatsLogFile + "*"); - // Log this message, but dont abort the thread - Trace.WriteLine(new LogMessage("ProcessQueueToXmds", "WebException: " + webEx.Message), LogType.Error.ToString()); + // Track processed files + int filesProcessed = 0; - // Drop out the loop - break; - } - catch (Exception e) + // Loop through each file + foreach (string fileName in logFiles) { - Trace.WriteLine(new LogMessage("FlushToXmds", string.Format("Exception when submitting to XMDS: {0}", e.Message)), LogType.Error.ToString()); + // Only process as many files in one go as configured + if (filesProcessed >= ApplicationSettings.Default.MaxLogFileUploads) + break; + + // If we have some, create an XMDS object + using (xmds.xmds logtoXmds = new xmds.xmds()) + { + logtoXmds.Url = ApplicationSettings.Default.XiboClient_xmds_xmds; + + // construct the log message + StringBuilder builder = new StringBuilder(); + builder.Append(""); + + foreach (string entry in File.ReadAllLines(fileName)) + builder.Append(entry); + + builder.Append(""); + + try + { + logtoXmds.SubmitStats(ApplicationSettings.Default.ServerKey, _hardwareKey.Key, builder.ToString()); + + // Delete the file we are on + File.Delete(fileName); + } + catch (WebException webEx) + { + // Increment the quantity of XMDS failures and bail out + ApplicationSettings.Default.IncrementXmdsErrorCount(); + + // Log this message, but dont abort the thread + Trace.WriteLine(new LogMessage("ProcessQueueToXmds", "WebException: " + webEx.Message), LogType.Error.ToString()); + + // Drop out the loop + break; + } + catch (Exception e) + { + Trace.WriteLine(new LogMessage("FlushToXmds", string.Format("Exception when submitting to XMDS: {0}", e.Message)), LogType.Error.ToString()); + } + + filesProcessed++; + } } } } - - // Log out - Debug.WriteLine(new LogMessage("FlushToXmds", String.Format("OUT")), LogType.Audit.ToString()); + catch (Exception e) + { + Trace.WriteLine(new LogMessage("FlushToXmds", string.Format("Unknown Exception: {0}", e.Message)), LogType.Error.ToString()); + } } } diff --git a/Log/XiboTraceListener.cs b/Log/XiboTraceListener.cs index 2db7a382..9b777311 100644 --- a/Log/XiboTraceListener.cs +++ b/Log/XiboTraceListener.cs @@ -1,6 +1,6 @@ /* * Xibo - Digitial Signage - http://www.xibo.org.uk - * Copyright (C) 2006-2012 Daniel Garner + * Copyright (C) 2006-2015 Daniel Garner, Spring Signage Ltd * * This file is part of Xibo. * @@ -35,6 +35,7 @@ namespace XiboClient { class XiboTraceListener : TraceListener { + public static object _locker = new object(); private Collection _traceMessages; private string _logPath; private HardwareKey _hardwareKey; @@ -146,53 +147,74 @@ private void FlushToFile() /// public void ProcessQueueToXmds() { - // If we haven't had a successful connection recently, then don't log - if (ApplicationSettings.Default.XmdsLastConnection.AddSeconds((int)ApplicationSettings.Default.CollectInterval) < DateTime.Now) - return; - - // Get a list of all the log files waiting to be sent to XMDS. - string[] logFiles = Directory.GetFiles(ApplicationSettings.Default.LibraryPath, "*" + ApplicationSettings.Default.LogLocation + "*"); - - foreach (string fileName in logFiles) + try { - // If we have some, create an XMDS object - using (xmds.xmds logtoXmds = new xmds.xmds()) - { - logtoXmds.Url = ApplicationSettings.Default.XiboClient_xmds_xmds; - - // construct the log message - StringBuilder builder = new StringBuilder(); - builder.Append(""); - - foreach (string entry in File.ReadAllLines(fileName)) - builder.Append(entry); - - builder.Append(""); + // If we haven't had a successful connection recently, then don't log + if (ApplicationSettings.Default.XmdsLastConnection.AddSeconds((int)ApplicationSettings.Default.CollectInterval) < DateTime.Now) + return; - try - { - logtoXmds.SubmitLog(ApplicationSettings.Default.ServerKey, _hardwareKey.Key, builder.ToString()); - - // Delete the file we are on - File.Delete(fileName); - } - catch (WebException webEx) - { - // Increment the quantity of XMDS failures and bail out - ApplicationSettings.Default.IncrementXmdsErrorCount(); + lock (_locker) + { + // Get a list of all the log files waiting to be sent to XMDS. + string[] logFiles = Directory.GetFiles(ApplicationSettings.Default.LibraryPath, "*" + ApplicationSettings.Default.LogLocation + "*"); - // Log this message, but dont abort the thread - Trace.WriteLine(new LogMessage("ProcessQueueToXmds", "WebException: " + webEx.Message), LogType.Error.ToString()); + // Track processed files + int filesProcessed = 0; - // Drop out the loop - break; - } - catch (Exception e) + // Loop through each file + foreach (string fileName in logFiles) { - Trace.WriteLine(new LogMessage("ProcessQueueToXmds", string.Format("Exception when submitting to XMDS: {0}", e.Message)), LogType.Error.ToString()); + // Only process as many files in one go as configured + if (filesProcessed >= ApplicationSettings.Default.MaxLogFileUploads) + break; + + // If we have some, create an XMDS object + using (xmds.xmds logtoXmds = new xmds.xmds()) + { + logtoXmds.Url = ApplicationSettings.Default.XiboClient_xmds_xmds; + + // construct the log message + StringBuilder builder = new StringBuilder(); + builder.Append(""); + + foreach (string entry in File.ReadAllLines(fileName)) + builder.Append(entry); + + builder.Append(""); + + try + { + logtoXmds.SubmitLog(ApplicationSettings.Default.ServerKey, _hardwareKey.Key, builder.ToString()); + + // Delete the file we are on + File.Delete(fileName); + } + catch (WebException webEx) + { + // Increment the quantity of XMDS failures and bail out + ApplicationSettings.Default.IncrementXmdsErrorCount(); + + // Log this message, but dont abort the thread + Trace.WriteLine(new LogMessage("ProcessQueueToXmds", "WebException: " + webEx.Message), LogType.Error.ToString()); + + // Drop out the loop + break; + } + catch (Exception e) + { + Trace.WriteLine(new LogMessage("ProcessQueueToXmds", string.Format("Exception when submitting to XMDS: {0}", e.Message)), LogType.Error.ToString()); + } + + filesProcessed++; + } } } } + catch (Exception e) + { + // Do nothing - we just have an unknown exception in logging + Trace.WriteLine(new LogMessage("ProcessQueueToXmds", string.Format("Unknown Exception: {0}", e.Message)), LogType.Error.ToString()); + } } public override void Write(string message) diff --git a/Logic/ApplicationSettings.cs b/Logic/ApplicationSettings.cs index 39657864..00432ae4 100644 --- a/Logic/ApplicationSettings.cs +++ b/Logic/ApplicationSettings.cs @@ -1,6 +1,6 @@ /* * Xibo - Digitial Signage - http://www.xibo.org.uk - * Copyright (C) 2006-14 Spring Signage Ltd + * Copyright (C) 2006-15 Spring Signage Ltd * * This file is part of Xibo. * @@ -36,9 +36,9 @@ public class ApplicationSettings private static string _default = "default"; // Application Specific Settings we want to protect - private string _clientVersion = "1.7.0"; + private string _clientVersion = "1.7.1"; private string _version = "4"; - private int _clientCodeVersion = 104; + private int _clientCodeVersion = 105; public string ClientVersion { get { return _clientVersion; } } public string Version { get { return _version; } } @@ -160,6 +160,9 @@ public object this[string propertyName] public int MaxConcurrentDownloads { get; set; } public int ScreenShotRequestInterval { get; set; } + private int _maxLogFileUploads; + public int MaxLogFileUploads { get { return ((_maxLogFileUploads == 0) ? 3 : _maxLogFileUploads); } set { _maxLogFileUploads = value; } } + public bool PowerpointEnabled { get; set; } public bool StatsEnabled { get; set; } public bool ExpireModifiedLayouts { get; set; } diff --git a/Logic/CacheManager.cs b/Logic/CacheManager.cs index 19b783b6..0df062c4 100644 --- a/Logic/CacheManager.cs +++ b/Logic/CacheManager.cs @@ -276,18 +276,6 @@ public bool IsValidLayout(string layoutFile) break; - case "localvideo": - - // Check that the path they have specified is ok - if (!File.Exists(Uri.UnescapeDataString(GetUri(media)).Replace('+', ' '))) - { - // Local video path does not exist - Trace.WriteLine(new LogMessage("CacheManager - IsValidLayout", media.InnerText + " does not exist"), LogType.Error.ToString()); - countInvalidLocalVideo++; - } - - break; - default: continue; } diff --git a/Logic/MediaOption.cs b/Logic/MediaOption.cs index ee39463e..21c1d7dd 100644 --- a/Logic/MediaOption.cs +++ b/Logic/MediaOption.cs @@ -70,7 +70,7 @@ public string Get(string name) return option.Value; } - throw new IndexOutOfRangeException("No such option"); + return string.Empty; } public string Get(string name, string def) diff --git a/MainForm.cs b/MainForm.cs index cbabb2d6..21db446c 100644 --- a/MainForm.cs +++ b/MainForm.cs @@ -45,7 +45,7 @@ public partial class MainForm : Form { private Schedule _schedule; private Collection _regions; - private bool _isExpired = false; + private bool _changingLayout = false; private int _scheduleId; private int _layoutId; private bool _screenSaver = false; @@ -398,7 +398,10 @@ private void SetCacheManager() /// void schedule_ScheduleChangeEvent(string layoutPath, int scheduleId, int layoutId) { - Trace.WriteLine(new LogMessage("MainForm - ScheduleChangeEvent", string.Format("Schedule Changing to {0}", layoutPath)), LogType.Audit.ToString()); + Trace.WriteLine(new LogMessage("MainForm - ScheduleChangeEvent", string.Format("Schedule Changing to {0}", layoutPath)), LogType.Audit.ToString()); + + // We are changing the layout + _changingLayout = true; _scheduleId = scheduleId; _layoutId = layoutId; @@ -429,32 +432,68 @@ private void ChangeToNextLayout(string layoutPath) try { SetThreadExecutionState(EXECUTION_STATE.ES_DISPLAY_REQUIRED | EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS); + } + catch + { + Trace.WriteLine(new LogMessage("MainForm - ChangeToNextLayout", "Unable to set Thread Execution state"), LogType.Info.ToString()); + } - // TODO: Check we are never out of the UI thread at this point - - DestroyLayout(); + try + { + // Destroy the Current Layout + try + { + DestroyLayout(); + } + catch (Exception e) + { + // Force collect all controls + foreach (System.Windows.Forms.Control control in Controls) + { + control.Dispose(); + Controls.Remove(control); + } - _isExpired = false; + Trace.WriteLine(new LogMessage("MainForm - ChangeToNextLayout", "Destroy Layout Failed. Exception raised was: " + e.Message), LogType.Error.ToString()); + throw e; + } - PrepareLayout(layoutPath); + // Prepare the next layout + try + { + PrepareLayout(layoutPath); + _clientInfoForm.CurrentLayoutId = layoutPath; - _clientInfoForm.CurrentLayoutId = layoutPath; + } + catch (Exception e) + { + DestroyLayout(); + Trace.WriteLine(new LogMessage("MainForm - ChangeToNextLayout", "Prepare Layout Failed. Exception raised was: " + e.Message), LogType.Error.ToString()); + throw e; + } // Do we need to notify? - if (ApplicationSettings.Default.SendCurrentLayoutAsStatusUpdate) + try { - using (xmds.xmds statusXmds = new xmds.xmds()) + if (ApplicationSettings.Default.SendCurrentLayoutAsStatusUpdate) { - statusXmds.Url = ApplicationSettings.Default.XiboClient_xmds_xmds; - statusXmds.NotifyStatusAsync(ApplicationSettings.Default.Version, ApplicationSettings.Default.ServerKey, ApplicationSettings.Default.HardwareKey, "{\"currentLayoutId\":" + _layoutId + "}"); + using (xmds.xmds statusXmds = new xmds.xmds()) + { + statusXmds.Url = ApplicationSettings.Default.XiboClient_xmds_xmds; + statusXmds.NotifyStatusAsync(ApplicationSettings.Default.Version, ApplicationSettings.Default.ServerKey, ApplicationSettings.Default.HardwareKey, "{\"currentLayoutId\":" + _layoutId + "}"); + } } } + catch (Exception e) + { + Trace.WriteLine(new LogMessage("MainForm - ChangeToNextLayout", "Notify Status Failed. Exception raised was: " + e.Message), LogType.Error.ToString()); + throw e; + } } catch (Exception ex) { Trace.WriteLine(new LogMessage("MainForm - ChangeToNextLayout", "Layout Change to " + layoutPath + " failed. Exception raised was: " + ex.Message), LogType.Error.ToString()); - _isExpired = true; - + ShowSplashScreen(); // In 10 seconds fire the next layout? @@ -465,6 +504,9 @@ private void ChangeToNextLayout(string layoutPath) // Start the timer timer.Start(); } + + // We have finished changing the layout + _changingLayout = false; } /// @@ -800,28 +842,42 @@ private static ImageCodecInfo GetEncoderInfo(string mimeType) /// void temp_DurationElapsedEvent() { - Debug.WriteLine("Region Elapsed", "MainForm - DurationElapsedEvent"); + Trace.WriteLine(new LogMessage("MainForm - DurationElapsedEvent", "Region Elapsed"), LogType.Audit.ToString()); - _isExpired = true; + // Are we already changing the layout? + if (_changingLayout) + { + Trace.WriteLine(new LogMessage("MainForm - DurationElapsedEvent", "Already Changing Layout"), LogType.Audit.ToString()); + return; + } + + bool isExpired = true; // Check the other regions to see if they are also expired. foreach (Region temp in _regions) { - if (!temp._hasExpired) + if (!temp.hasExpired()) { - _isExpired = false; + isExpired = false; + break; } } - if (_isExpired) + // If we are sure we have expired after checking all regions, then set the layout expired flag on them all + if (isExpired) { // Inform each region that the layout containing it has expired foreach (Region temp in _regions) { - temp._layoutExpired = true; + temp.setLayoutExpired(); } - System.Diagnostics.Debug.WriteLine("Region Expired - Next Region.", "MainForm - DurationElapsedEvent"); + Trace.WriteLine(new LogMessage("MainForm - DurationElapsedEvent", "All Regions have expired. Raising a Next layout event."), LogType.Audit.ToString()); + + // We are changing the layout + _changingLayout = true; + + // Yield and restart _schedule.NextLayout(); } } @@ -833,27 +889,34 @@ private void DestroyLayout() { Debug.WriteLine("Destroying Layout", "MainForm - DestoryLayout"); - if (_regions == null) return; + if (_regions == null) + return; - foreach (Region region in _regions) + lock (_regions) { - region.Clear(); - - this.Controls.Remove(region); - - try - { - System.Diagnostics.Debug.WriteLine("Calling Dispose Region", "MainForm - DestoryLayout"); - region.Dispose(); - } - catch (Exception e) + foreach (Region region in _regions) { - //do nothing (perhaps write to some error xml somewhere?) - System.Diagnostics.Debug.WriteLine(e.Message); + try + { + // Remove the region from the list of controls + Controls.Remove(region); + + // Clear the region + region.Clear(); + + Trace.WriteLine(new LogMessage("MainForm - DestoryLayout", "Calling Dispose on Region " + region.regionOptions.regionId), LogType.Audit.ToString()); + region.Dispose(); + } + catch (Exception e) + { + // If we can't dispose we should log to understand why + Trace.WriteLine(new LogMessage("MainForm - DestoryLayout", e.Message), LogType.Info.ToString()); + } } + + _regions.Clear(); } - _regions.Clear(); _regions = null; } diff --git a/Media/Flash.cs b/Media/Flash.cs index ab4fdd17..032053e2 100644 --- a/Media/Flash.cs +++ b/Media/Flash.cs @@ -34,7 +34,8 @@ class Flash private string _backgroundColor; private string _backgroundTop; private string _backgroundLeft; - + private bool _disposed = false; + public Flash (RegionOptions options) : base(options.width, options.height, options.top, options.left) { @@ -71,6 +72,10 @@ public Flash (RegionOptions options) // Navigate to temp file _webBrowser.Navigate(_tempHtml.Path); + Controls.Add(_webBrowser); + + // Show the control + Show(); } /// @@ -101,9 +106,12 @@ private void GenerateHeadHtml() /// void _webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) { - // We have navigated to the temporary file. - Show(); - Controls.Add(_webBrowser); + base.StartTimer(); + + if (_disposed) + return; + + _webBrowser.Visible = true; } /// @@ -112,6 +120,7 @@ void _webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEve /// protected override void Dispose(bool disposing) { + _disposed = true; if (disposing) { // Remove the webbrowser control diff --git a/Media/Media.cs b/Media/Media.cs index 5d3525f3..0728e692 100644 --- a/Media/Media.cs +++ b/Media/Media.cs @@ -236,6 +236,7 @@ protected override void Dispose(bool disposing) { try { + // Dispose of the Timer if (_timer != null) _timer.Dispose(); } diff --git a/Media/Video.cs b/Media/Video.cs index 7c96d9c4..7c47a3d1 100644 --- a/Media/Video.cs +++ b/Media/Video.cs @@ -54,8 +54,7 @@ public Video(RegionOptions options) _videoPlayer.Location = new System.Drawing.Point(0, 0); // Should we loop? - if (options.Dictionary.Get("loop", "0") == "1" && _duration == 0) - _videoPlayer.SetLooping(true); + _videoPlayer.SetLooping((options.Dictionary.Get("loop", "0") == "1" && _duration != 0)); // Should we mute? _videoPlayer.SetMute((options.Dictionary.Get("mute", "0") == "1")); @@ -129,6 +128,7 @@ void _videoPlayer_VideoEnd() // Immediately hide the player _videoPlayer.Hide(); + // Set to expired _expired = true; } } @@ -148,13 +148,18 @@ protected override void Dispose(bool disposing) { try { - _videoPlayer.Hide(); + // Stop and Clear + _videoPlayer.StopAndClear(); + + // Remove the control Controls.Remove(_videoPlayer); + + // Dispose of the Control _videoPlayer.Dispose(); } - catch + catch (Exception e) { - + Trace.WriteLine(new LogMessage("Video - Dispose", "Problem disposing of the Video Player. Ex = " + e.Message), LogType.Audit.ToString()); } base.Dispose(disposing); diff --git a/Media/VideoPlayer.Designer.cs b/Media/VideoPlayer.Designer.cs index f979eb45..d6956d6e 100644 --- a/Media/VideoPlayer.Designer.cs +++ b/Media/VideoPlayer.Designer.cs @@ -59,5 +59,6 @@ private void InitializeComponent() #endregion private AxWMPLib.AxWindowsMediaPlayer axWindowsMediaPlayer1; + } } \ No newline at end of file diff --git a/Media/VideoPlayer.cs b/Media/VideoPlayer.cs index dc7003e3..fc6b3bc8 100644 --- a/Media/VideoPlayer.cs +++ b/Media/VideoPlayer.cs @@ -25,6 +25,7 @@ using System.Text; using System.Windows.Forms; using System.Diagnostics; +using System.Runtime.InteropServices; /// 09/06/12 Dan Changed to raise an event when the video is finished /// 04/08/12 Dan Changed to raise an error event if one is raised from the control @@ -41,6 +42,8 @@ public partial class VideoPlayer : Form public delegate void VideoErrored(); public event VideoErrored VideoError; + private bool _looping = false; + public VideoPlayer() { InitializeComponent(); @@ -59,7 +62,7 @@ public void StartPlayer(string filePath) axWindowsMediaPlayer1.uiMode = "none"; axWindowsMediaPlayer1.URL = filePath; axWindowsMediaPlayer1.stretchToFit = true; - axWindowsMediaPlayer1.windowlessVideo = true; + axWindowsMediaPlayer1.windowlessVideo = false; axWindowsMediaPlayer1.PlayStateChange += new AxWMPLib._WMPOCXEvents_PlayStateChangeEventHandler(axWMP_PlayStateChange); axWindowsMediaPlayer1.ErrorEvent += new EventHandler(axWindowsMediaPlayer1_ErrorEvent); @@ -68,6 +71,7 @@ public void StartPlayer(string filePath) public void SetLooping(bool looping) { axWindowsMediaPlayer1.settings.setMode("loop", looping); + _looping = looping; } public void SetMute(bool mute) @@ -78,6 +82,40 @@ public void SetMute(bool mute) axWindowsMediaPlayer1.settings.volume = 100; } + public void StopAndClear() + { + try + { + GC.WaitForPendingFinalizers(); + GC.Collect(); + + if (axWindowsMediaPlayer1 != null) + { + // Unbind events + axWindowsMediaPlayer1.PlayStateChange -= axWMP_PlayStateChange; + axWindowsMediaPlayer1.ErrorEvent -= axWindowsMediaPlayer1_ErrorEvent; + + // Release resources + Marshal.FinalReleaseComObject(axWindowsMediaPlayer1.currentMedia); + axWindowsMediaPlayer1.URL = null; + axWindowsMediaPlayer1.close(); + + // Remove the WMP control + Controls.Remove(axWindowsMediaPlayer1); + + // Close this form + Close(); + + //axWindowsMediaPlayer1.Dispose(); + axWindowsMediaPlayer1 = null; + } + } + catch (AccessViolationException) + { + + } + } + void axWindowsMediaPlayer1_ErrorEvent(object sender, EventArgs e) { // Get the error for logging @@ -91,20 +129,37 @@ void axWindowsMediaPlayer1_ErrorEvent(object sender, EventArgs e) error = "Unknown Error"; } - Trace.WriteLine(new LogMessage("VideoPlayer - ErrorEvent", error), LogType.Error.ToString()); + Trace.WriteLine(new LogMessage("VideoPlayer - ErrorEvent", axWindowsMediaPlayer1.URL + ". Ex = " + error), LogType.Error.ToString()); // Raise the event - VideoError(); + if (VideoError == null) + { + // The parent form has been ditached and disposed + StopAndClear(); + } + else + { + VideoError(); + } } void axWMP_PlayStateChange(object sender, AxWMPLib._WMPOCXEvents_PlayStateChangeEvent e) { - if (e.newState == 8) + if (e.newState == 8 && !_looping) { // indicate we are stopped _finished = true; - VideoEnd(); + // Raise the event + if (VideoEnd == null) + { + // The parent form has been ditached and disposed + StopAndClear(); + } + else + { + VideoEnd(); + } } } diff --git a/Media/VideoPlayer.resx b/Media/VideoPlayer.resx index f30319b7..2769d211 100644 --- a/Media/VideoPlayer.resx +++ b/Media/VideoPlayer.resx @@ -112,14 +112,14 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj0yLjAuMC4w + AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACFTeXN0 ZW0uV2luZG93cy5Gb3Jtcy5BeEhvc3QrU3RhdGUBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAuQAAAAIB AAAAAQAAAAAAAAAAAAAAAKQAAAAAAwAACAACAAAAAAAFAAAAAAAAAPA/AwAAAAAABQAAAAAAAAAAAAgA diff --git a/Program.cs b/Program.cs index 736dfc55..c5779161 100644 --- a/Program.cs +++ b/Program.cs @@ -1,6 +1,6 @@ /* * Xibo - Digitial Signage - http://www.xibo.org.uk - * Copyright (C) 2006-2012 Daniel Garner and James Packer + * Copyright (C) 2006-2015 Daniel Garner and the Xibo Developers * * This file is part of Xibo. * @@ -24,9 +24,6 @@ using System.Diagnostics; using Xilium.CefGlue; -// 17/08/2012 Dan Set process priority to RealTime -// 21/08/2012 Dan Only enable visual styles for Options Form - namespace XiboClient { static class Program @@ -37,35 +34,39 @@ static class Program [STAThread] static int Main(string[] args) { - try - { - CefRuntime.Load(); - } - catch (DllNotFoundException ex) - { - MessageBox.Show(ex.Message, "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error); - return 1; - } - catch (CefRuntimeException ex) + // Do we need to initialise CEF? + if (ApplicationSettings.Default.UseCefWebBrowser) { - MessageBox.Show(ex.Message, "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error); - return 2; - } - catch (Exception ex) - { - MessageBox.Show(ex.ToString(), "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error); - return 3; - } + try + { + CefRuntime.Load(); + } + catch (DllNotFoundException ex) + { + MessageBox.Show(ex.Message, "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error); + return 1; + } + catch (CefRuntimeException ex) + { + MessageBox.Show(ex.Message, "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error); + return 2; + } + catch (Exception ex) + { + MessageBox.Show(ex.ToString(), "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error); + return 3; + } - var settings = new CefSettings(); - settings.MultiThreadedMessageLoop = true; - settings.SingleProcess = false; - settings.LogSeverity = CefLogSeverity.Disable; - settings.LogFile = "cef.log"; - settings.ResourcesDirPath = System.IO.Path.GetDirectoryName(new Uri(System.Reflection.Assembly.GetEntryAssembly().CodeBase).LocalPath); - settings.RemoteDebuggingPort = 20480; + var settings = new CefSettings(); + settings.MultiThreadedMessageLoop = true; + settings.SingleProcess = false; + settings.LogSeverity = CefLogSeverity.Disable; + settings.LogFile = "cef.log"; + settings.ResourcesDirPath = System.IO.Path.GetDirectoryName(new Uri(System.Reflection.Assembly.GetEntryAssembly().CodeBase).LocalPath); + settings.RemoteDebuggingPort = 20480; - CefRuntime.Initialize(new CefMainArgs(args), settings, null, IntPtr.Zero); + CefRuntime.Initialize(new CefMainArgs(args), settings, null, IntPtr.Zero); + } // Ensure our process has the highest priority Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.RealTime; @@ -130,7 +131,9 @@ static int Main(string[] args) Trace.WriteLine(new LogMessage("Main", "Application Finished"), LogType.Info.ToString()); Trace.Flush(); - CefRuntime.Shutdown(); + if (ApplicationSettings.Default.UseCefWebBrowser) + CefRuntime.Shutdown(); + return 0; } @@ -182,7 +185,9 @@ static void HandleUnhandledException(Object o) // TODO: Can we just restart the application? // Shutdown the application - CefRuntime.Shutdown(); + if (ApplicationSettings.Default.UseCefWebBrowser) + CefRuntime.Shutdown(); + Environment.Exit(1); } diff --git a/XiboClient.csproj b/XiboClient.csproj index 38179d02..5eeaac3c 100644 --- a/XiboClient.csproj +++ b/XiboClient.csproj @@ -301,7 +301,6 @@ -