#region Copyright (C) 2005-2010 Team MediaPortal // Copyright (C) 2005-2010 Team MediaPortal // http://www.team-mediaportal.com // // MediaPortal is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 2 of the License, or // (at your option) any later version. // // MediaPortal is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with MediaPortal. If not, see . #endregion #region Usings using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Configuration; using System.Diagnostics; using System.Drawing; using System.Globalization; using System.IO; using System.Net; using System.Reflection; using System.Text; using System.Threading; using System.Windows.Forms; using MediaPortal; using MediaPortal.Configuration; using MediaPortal.Dialogs; using MediaPortal.GUI.Library; using MediaPortal.Player; using MediaPortal.Profile; using MediaPortal.Util; using TvControl; using TvDatabase; using TvLibrary.Implementations.DVB; using TvLibrary.Interfaces; using Action = MediaPortal.GUI.Library.Action; #endregion namespace TvPlugin { /// /// TV Home screen. /// [PluginIcons("TvPlugin.TVPlugin.gif", "TvPlugin.TVPluginDisabled.gif")] public class TVHome : GUIInternalWindow, ISetupForm, IShowPlugin, IPluginReceiver { #region constants private const int HEARTBEAT_INTERVAL = 1; //seconds private const int MAX_WAIT_FOR_SERVER_CONNECTION = 10; //seconds private const int WM_POWERBROADCAST = 0x0218; private const int WM_QUERYENDSESSION = 0x0011; private const int PBT_APMQUERYSUSPEND = 0x0000; private const int PBT_APMQUERYSTANDBY = 0x0001; private const int PBT_APMQUERYSUSPENDFAILED = 0x0002; private const int PBT_APMQUERYSTANDBYFAILED = 0x0003; private const int PBT_APMSUSPEND = 0x0004; private const int PBT_APMSTANDBY = 0x0005; private const int PBT_APMRESUMECRITICAL = 0x0006; private const int PBT_APMRESUMESUSPEND = 0x0007; private const int PBT_APMRESUMESTANDBY = 0x0008; private const int PBT_APMRESUMEAUTOMATIC = 0x0012; private const int PROGRESS_PERCENTAGE_UPDATE_INTERVAL = 1000; private const int PROCESS_UPDATE_INTERVAL = 1000; #endregion #region variables private enum Controls { IMG_REC_CHANNEL = 21, LABEL_REC_INFO = 22, IMG_REC_RECTANGLE = 23, } [Flags] public enum LiveTvStatus { WasPlaying = 1, CardChange = 2, SeekToEnd = 4, SeekToEndAfterPlayback = 8 } private Channel _resumeChannel = null; private Thread heartBeatTransmitterThread = null; private static DateTime _updateProgressTimer = DateTime.MinValue; private static ChannelNavigator m_navigator; private static TVUtil _util; private static VirtualCard _card = null; private static DateTime _updateTimer = DateTime.Now; private static bool _autoTurnOnTv = false; private static int _waitonresume = 0; public static bool settingsLoaded = false; private TvCropManager _cropManager = new TvCropManager(); private TvNotifyManager _notifyManager = new TvNotifyManager(); private static List _preferredLanguages; private static bool _usertsp; private static string _recordingpath = ""; private static string _timeshiftingpath = ""; private static bool _preferAC3 = false; private static bool _preferAudioTypeOverLang = false; private static bool _autoFullScreen = false; private static bool _suspended = false; private static bool _showlastactivemodule = false; private static bool _showlastactivemoduleFullscreen = false; private static bool _playbackStopped = false; private static bool _onPageLoadDone = false; private static bool _userChannelChanged = false; private static bool _showChannelStateIcons = true; private static bool _doingHandleServerNotConnected = false; private static bool _doingChannelChange = false; private static bool _ServerNotConnectedHandled = false; private static bool _recoverTV = false; private static bool _connected = false; private static bool _isAnyCardRecording = false; protected static TvServer _server; private static ManualResetEvent _waitForBlackScreen = null; private static ManualResetEvent _waitForVideoReceived = null; private static int FramesBeforeStopRenderBlackImage = 0; private static BitHelper _status = new BitHelper(); [SkinControl(2)] protected GUIButtonControl btnTvGuide = null; [SkinControl(3)] protected GUIButtonControl btnRecord = null; [SkinControl(7)] protected GUIButtonControl btnChannel = null; [SkinControl(8)] protected GUIToggleButtonControl btnTvOnOff = null; [SkinControl(13)] protected GUIButtonControl btnTeletext = null; [SkinControl(24)] protected GUIImage imgRecordingIcon = null; [SkinControl(99)] protected GUIVideoControl videoWindow = null; [SkinControl(9)] protected GUIButtonControl btnActiveStreams = null; [SkinControl(14)] protected GUIButtonControl btnActiveRecordings = null; // error handling public class ChannelErrorInfo { public Channel FailingChannel; public TvResult Result; public List Messages = new List(); } public static ChannelErrorInfo _lastError = new ChannelErrorInfo(); // CI Menu private static CiMenuHandler ciMenuHandler; public static GUIDialogCIMenu dlgCiMenu; public static GUIDialogNotify _dialogNotify = null; private static CiMenu currentCiMenu = null; private static object CiMenuLock = new object(); private static bool CiMenuActive = false; private static List CiMenuList = new List(); // EPG Channel private static Channel _lastTvChannel = null; // notification protected static int _notifyTVTimeout = 15; protected static bool _playNotifyBeep = true; protected static int _preNotifyConfig = 60; #endregion #region events & delegates private static event OnChannelChangedDelegate OnChannelChanged; private delegate void OnChannelChangedDelegate(); #endregion #region delegates private delegate void StopPlayerMainThreadDelegate(); #endregion #region ISetupForm Members public bool CanEnable() { return true; } public string PluginName() { return "TV"; } public bool DefaultEnabled() { return true; } public int GetWindowId() { return (int)Window.WINDOW_TV; } public bool GetHome(out string strButtonText, out string strButtonImage, out string strButtonImageFocus, out string strPictureImage) { // TODO: Add TVHome.GetHome implementation strButtonText = GUILocalizeStrings.Get(605); strButtonImage = ""; strButtonImageFocus = ""; strPictureImage = @"hover_my tv.png"; return true; } public string Author() { return "Frodo, gemx"; } public string Description() { return "Connect to TV service to watch, record and timeshift analog and digital TV"; } public bool HasSetup() { return false; } public void ShowPlugin() { } #endregion #region IShowPlugin Members public bool ShowDefaultHome() { return true; } #endregion public TVHome() { TVUtil.SetGentleConfigFile(); GetID = (int)Window.WINDOW_TV; } #region Overrides public override bool Init() { return Load(GUIGraphicsContext.Skin + @"\mytvhomeServer.xml"); } public override void OnAdded() { Log.Info("TVHome:OnAdded"); RemoteControl.OnRemotingDisconnected += new RemoteControl.RemotingDisconnectedDelegate(RemoteControl_OnRemotingDisconnected); RemoteControl.OnRemotingConnected += new RemoteControl.RemotingConnectedDelegate(RemoteControl_OnRemotingConnected); GUIGraphicsContext.OnBlackImageRendered += new BlackImageRenderedHandler(OnBlackImageRendered); GUIGraphicsContext.OnVideoReceived += new VideoReceivedHandler(OnVideoReceived); _waitForBlackScreen = new ManualResetEvent(false); _waitForVideoReceived = new ManualResetEvent(false); Application.ApplicationExit += new EventHandler(Application_ApplicationExit); g_Player.PlayBackStarted += new g_Player.StartedHandler(OnPlayBackStarted); g_Player.PlayBackStopped += new g_Player.StoppedHandler(OnPlayBackStopped); g_Player.AudioTracksReady += new g_Player.AudioTracksReadyHandler(OnAudioTracksReady); GUIWindowManager.Receivers += new SendMessageHandler(OnGlobalMessage); // replace g_player's ShowFullScreenWindowTV g_Player.ShowFullScreenWindowTV = ShowFullScreenWindowTVHandler; try { // Make sure that we have valid hostname for the TV server SetRemoteControlHostName(); // Wake up the TV server, if required HandleWakeUpTvServer(); startHeartBeatThread(); TVHome.OnChannelChanged -= new OnChannelChangedDelegate(ForceUpdates); TVHome.OnChannelChanged += new OnChannelChangedDelegate(ForceUpdates); m_navigator = new ChannelNavigator(); m_navigator.OnZapChannel -= new ChannelNavigator.OnZapChannelDelegate(ForceUpdates); m_navigator.OnZapChannel += new ChannelNavigator.OnZapChannelDelegate(ForceUpdates); LoadSettings(); string pluginVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion; string tvServerVersion = Connected ? RemoteControl.Instance.GetAssemblyVersion : "Unknown"; if (Connected && pluginVersion != tvServerVersion) { string strLine = "TvPlugin and TvServer don't have the same version.\r\n"; strLine += "TvServer Version: " + tvServerVersion + "\r\n"; strLine += "TvPlugin Version: " + pluginVersion; Log.Error(strLine); } else Log.Info("TVHome V" + pluginVersion + ":ctor"); } catch (Exception ex) { Log.Error("TVHome: Error occured in Init(): {0}, st {1}", ex.Message, Environment.StackTrace); } _notifyManager.Start(); } /// /// Gets called by the runtime when a window will be destroyed /// Every window window should override this method and cleanup any resources /// /// public override void DeInit() { OnPageDestroy(-1); RemoteControl.OnRemotingDisconnected -= new RemoteControl.RemotingDisconnectedDelegate(RemoteControl_OnRemotingDisconnected); RemoteControl.OnRemotingConnected -= new RemoteControl.RemotingConnectedDelegate(RemoteControl_OnRemotingConnected); GUIGraphicsContext.OnBlackImageRendered -= new BlackImageRenderedHandler(OnBlackImageRendered); GUIGraphicsContext.OnVideoReceived -= new VideoReceivedHandler(OnVideoReceived); Application.ApplicationExit -= new EventHandler(Application_ApplicationExit); g_Player.PlayBackStarted -= new g_Player.StartedHandler(OnPlayBackStarted); g_Player.PlayBackStopped -= new g_Player.StoppedHandler(OnPlayBackStopped); g_Player.AudioTracksReady -= new g_Player.AudioTracksReadyHandler(OnAudioTracksReady); GUIWindowManager.Receivers -= new SendMessageHandler(OnGlobalMessage); } public override bool SupportsDelayedLoad { get { return false; } } public override void OnAction(Action action) { switch (action.wID) { case Action.ActionType.ACTION_RECORD: // record current program on current channel // are we watching tv? ManualRecord(Navigator.Channel, GetID); break; case Action.ActionType.ACTION_PREV_CHANNEL: OnPreviousChannel(); break; case Action.ActionType.ACTION_PAGE_DOWN: OnPreviousChannel(); break; case Action.ActionType.ACTION_NEXT_CHANNEL: OnNextChannel(); break; case Action.ActionType.ACTION_PAGE_UP: OnNextChannel(); break; case Action.ActionType.ACTION_LAST_VIEWED_CHANNEL: OnLastViewedChannel(); break; case Action.ActionType.ACTION_PREVIOUS_MENU: { // goto home // are we watching tv & doing timeshifting // No, then stop viewing... //g_Player.Stop(); GUIWindowManager.ShowPreviousWindow(); return; } case Action.ActionType.ACTION_KEY_PRESSED: { if ((char)action.m_key.KeyChar == '0') { OnLastViewedChannel(); } } break; case Action.ActionType.ACTION_SHOW_GUI: { // If we are in tvhome and TV is currently off and no fullscreen TV then turn ON TV now! if (!g_Player.IsTimeShifting && !g_Player.FullScreen) { OnClicked(8, btnTvOnOff, Action.ActionType.ACTION_MOUSE_CLICK); //8=togglebutton } break; } } base.OnAction(action); } protected override void OnPageLoad() { Log.Info("TVHome:OnPageLoad"); if (GUIWindowManager.GetWindow(GUIWindowManager.ActiveWindow).PreviousWindowId != (int)Window.WINDOW_TVFULLSCREEN) { _playbackStopped = false; } btnActiveStreams.Label = GUILocalizeStrings.Get(692); if (!Connected) { if (!_onPageLoadDone) { RemoteControl.Clear(); GUIWindowManager.ActivateWindow((int)Window.WINDOW_SETTINGS_TVENGINE); return; } else { UpdateStateOfRecButton(); UpdateProgressPercentageBar(); UpdateRecordingIndicator(); return; } } try { int cards = RemoteControl.Instance.Cards; } catch (Exception) { RemoteControl.Clear(); } // stop the old recorder. // DatabaseManager.Instance.DefaultQueryStrategy = QueryStrategy.DataSourceOnly; GUIMessage msgStopRecorder = new GUIMessage(GUIMessage.MessageType.GUI_MSG_RECORDER_STOP, 0, 0, 0, 0, 0, null); GUIWindowManager.SendMessage(msgStopRecorder); if (!_onPageLoadDone && m_navigator != null) { m_navigator.ReLoad(); } if (m_navigator == null) { m_navigator = new ChannelNavigator(); // Create the channel navigator (it will load groups and channels) } base.OnPageLoad(); // set video window position if (videoWindow != null) { GUIGraphicsContext.VideoWindow = new Rectangle(videoWindow.XPosition, videoWindow.YPosition, videoWindow.Width, videoWindow.Height); } // start viewing tv... GUIGraphicsContext.IsFullScreenVideo = false; Channel channel = Navigator.Channel; if (channel == null || channel.IsRadio) { if (Navigator.CurrentGroup != null && Navigator.Groups.Count > 0) { Navigator.SetCurrentGroup(Navigator.Groups[0].GroupName); GUIPropertyManager.SetProperty("#TV.Guide.Group", Navigator.Groups[0].GroupName); } if (Navigator.CurrentGroup != null) { if (Navigator.CurrentGroup.ReferringGroupMap().Count > 0) { GroupMap gm = (GroupMap)Navigator.CurrentGroup.ReferringGroupMap()[0]; channel = gm.ReferencedChannel(); } } } if (channel != null) { Log.Info("tv home init:{0}", channel.DisplayName); if (!_suspended) { AutoTurnOnTv(channel); } else { _resumeChannel = channel; } GUIPropertyManager.SetProperty("#TV.Guide.Group", Navigator.CurrentGroup.GroupName); Log.Info("tv home init:{0} done", channel.DisplayName); } if (!_suspended) { AutoFullScreenTv(); } _onPageLoadDone = true; _suspended = false; UpdateGUIonPlaybackStateChange(); UpdateCurrentChannel(); } private void AutoTurnOnTv(Channel channel) { if (_autoTurnOnTv && !_playbackStopped && !wasPrevWinTVplugin()) { if (!wasPrevWinTVplugin()) { _userChannelChanged = false; } ViewChannelAndCheck(channel); } } private void AutoFullScreenTv() { if (_autoFullScreen) { // if using showlastactivemodule feature and last module is fullscreen while returning from powerstate, then do not set fullscreen here (since this is done by the resume last active module feature) // we depend on the onresume method, thats why tvplugin now impl. the IPluginReceiver interface. if (!_suspended) { bool isTvOrRec = (g_Player.IsTV || g_Player.IsTVRecording); if (isTvOrRec) { Log.Debug("GUIGraphicsContext.IsFullScreenVideo {0}", GUIGraphicsContext.IsFullScreenVideo); bool wasFullScreenTV = (PreviousWindowId == (int)Window.WINDOW_TVFULLSCREEN); if (!wasFullScreenTV) { if (!wasPrevWinTVplugin()) { Log.Debug("TVHome.AutoFullScreenTv(): setting autoFullScreen"); bool showlastActModFS = (_showlastactivemodule && _showlastactivemoduleFullscreen && !_suspended && _autoTurnOnTv); if (!showlastActModFS) { //if we are resuming from standby with tvhome, we want this in fullscreen, but we need a delay for it to work. Thread tvDelayThread = new Thread(TvDelayThread); tvDelayThread.Start(); } else { g_Player.ShowFullScreenWindow(); } } else { g_Player.ShowFullScreenWindow(); } } } } } } protected override void OnPageDestroy(int newWindowId) { // if we're switching to another plugin if (!GUIGraphicsContext.IsTvWindow(newWindowId)) { //and we're not playing which means we dont timeshift tv //g_Player.Stop(); } if (Connected) { SaveSettings(); } base.OnPageDestroy(newWindowId); } protected override void OnClicked(int controlId, GUIControl control, Action.ActionType actionType) { Stopwatch benchClock = null; benchClock = Stopwatch.StartNew(); RefreshConnectionState(); if (!Connected) { UpdateStateOfRecButton(); UpdateRecordingIndicator(); UpdateGUIonPlaybackStateChange(); ShowDlgAsynch(); return; } if (control == btnActiveStreams) { OnActiveStreams(); } if (control == btnActiveRecordings && btnActiveRecordings != null) { OnActiveRecordings(); } if (control == btnTvOnOff) { if (Card.IsTimeShifting && g_Player.IsTV && g_Player.Playing) { // tv off g_Player.Stop(); Log.Warn("TVHome.OnClicked(): EndTvOff {0} ms", benchClock.ElapsedMilliseconds.ToString()); benchClock.Stop(); return; } else { // tv on Log.Info("TVHome:turn tv on {0}", Navigator.CurrentChannel); // stop playing anything if (g_Player.Playing) { if (g_Player.IsTV && !g_Player.IsTVRecording) { //already playing tv... } else { Log.Warn("TVHome.OnClicked: Stop Called - {0} ms", benchClock.ElapsedMilliseconds.ToString()); g_Player.Stop(true); } } } // turn tv on/off if (Navigator.Channel.IsTv) { ViewChannelAndCheck(Navigator.Channel); } else // current channel seems to be non-tv (radio ?), get latest known tv channel from xml config and use this instead { Settings xmlreader = new MPSettings(); string currentchannelName = xmlreader.GetValueAsString("mytv", "channel", String.Empty); Channel currentChannel = Navigator.GetChannel(currentchannelName); ViewChannelAndCheck(currentChannel); } UpdateStateOfRecButton(); UpdateGUIonPlaybackStateChange(); //UpdateProgressPercentageBar(); benchClock.Stop(); Log.Warn("TVHome.OnClicked(): Total Time - {0} ms", benchClock.ElapsedMilliseconds.ToString()); } if (control == btnTeletext) { GUIWindowManager.ActivateWindow((int)Window.WINDOW_TELETEXT); return; } if (control == btnRecord) { OnRecord(); } if (control == btnChannel) { OnSelectChannel(); } base.OnClicked(controlId, control, actionType); } public override bool OnMessage(GUIMessage message) { switch (message.Message) { case GUIMessage.MessageType.PS_ONSTANDBY: RemoteControl.Clear(); break; case GUIMessage.MessageType.GUI_MSG_RESUME_TV: { // we only want to resume TV if previous window is NOT a tvplugin based one. (ex. tvguide.) if (_autoTurnOnTv && !wasPrevWinTVplugin()) { //restart viewing... Log.Info("tv home msg resume tv:{0}", Navigator.CurrentChannel); ViewChannel(Navigator.Channel); } } break; } return base.OnMessage(message); } private static void ForceUpdates() { _updateTimer = DateTime.Now.AddMilliseconds(-1 * (PROCESS_UPDATE_INTERVAL+1)); _updateProgressTimer = DateTime.Now.AddMilliseconds(-1 * (PROGRESS_PERCENTAGE_UPDATE_INTERVAL+1)); } public override void Process() { TimeSpan ts = DateTime.Now - _updateTimer; if (!Connected || _suspended || ts.TotalMilliseconds < PROCESS_UPDATE_INTERVAL) { return; } try { UpdateRecordingIndicator(); UpdateStateOfRecButton(); if (!Card.IsTimeShifting) { UpdateProgressPercentageBar(); // mantis #2218 : TV guide information in TV home screen does not update when program changes if TV is not playing return; } // BAV, 02.03.08: a channel change should not be delayed by rendering. // by moving thisthe 1 min delays in zapping should be fixed // Let the navigator zap channel if needed if (Navigator.CheckChannelChange()) { UpdateGUIonPlaybackStateChange(); } if (GUIGraphicsContext.InVmr9Render) { return; } ShowCiMenu(); UpdateCurrentChannel(); } finally { _updateTimer = DateTime.Now; } } public override bool IsTv { get { return true; } } #endregion #region Public static methods public static void StartRecordingSchedule(Channel channel, bool manual) { TvBusinessLayer layer = new TvBusinessLayer(); TvServer server = new TvServer(); if (manual) // until manual stop { Schedule newSchedule = new Schedule(channel.IdChannel, GUILocalizeStrings.Get(413) + " (" + channel.DisplayName + ")", DateTime.Now, DateTime.Now.AddDays(1)); newSchedule.PreRecordInterval = Int32.Parse(layer.GetSetting("preRecordInterval", "5").Value); newSchedule.PostRecordInterval = Int32.Parse(layer.GetSetting("postRecordInterval", "5").Value); newSchedule.Persist(); server.OnNewSchedule(); } else // current program { // lets find any canceled episodes that match this one we want to create, if found uncancel it. Schedule existingParentSchedule = Schedule.RetrieveSeries(channel.IdChannel, channel.CurrentProgram.Title, channel.CurrentProgram.StartTime, channel.CurrentProgram.EndTime); if (existingParentSchedule != null) { foreach (CanceledSchedule cancelSched in existingParentSchedule.ReferringCanceledSchedule()) { if (cancelSched.CancelDateTime == channel.CurrentProgram.StartTime) { existingParentSchedule.UnCancelSerie(channel.CurrentProgram.StartTime, channel.CurrentProgram.IdChannel); server.OnNewSchedule(); return; } } } // ok, no existing schedule found with matching canceled schedules found. proceeding to add the schedule normally Schedule newSchedule = new Schedule(channel.IdChannel, channel.CurrentProgram.Title, channel.CurrentProgram.StartTime, channel.CurrentProgram.EndTime); newSchedule.PreRecordInterval = Int32.Parse(layer.GetSetting("preRecordInterval", "5").Value); newSchedule.PostRecordInterval = Int32.Parse(layer.GetSetting("postRecordInterval", "5").Value); newSchedule.Persist(); server.OnNewSchedule(); } } public static bool UseRTSP() { if (!settingsLoaded) { LoadSettings(); } return _usertsp; } public static bool ShowChannelStateIcons() { return _showChannelStateIcons; } public static string RecordingPath() { return _recordingpath; } public static string TimeshiftingPath() { return _timeshiftingpath; } public static bool DoingChannelChange() { return _doingChannelChange; } private static void StopPlayerMainThread() { //call g_player.stop only on main thread. if (GUIGraphicsContext.form.InvokeRequired) { StopPlayerMainThreadDelegate d = new StopPlayerMainThreadDelegate(StopPlayerMainThread); GUIGraphicsContext.form.Invoke(d); return; } g_Player.Stop(); } private delegate void ShowDlgAsynchDelegate(); private delegate void ShowDlgMessageAsynchDelegate(String Message); private static void ShowDlgAsynch() { //show dialogue only on main thread. if (GUIGraphicsContext.form.InvokeRequired) { ShowDlgAsynchDelegate d = new ShowDlgAsynchDelegate(ShowDlgAsynch); GUIGraphicsContext.form.Invoke(d); return; } _ServerNotConnectedHandled = true; GUIDialogOK pDlgOK = (GUIDialogOK)GUIWindowManager.GetWindow((int)Window.WINDOW_DIALOG_OK); pDlgOK.Reset(); pDlgOK.SetHeading(257); //error if (Navigator != null && Navigator.CurrentChannel != null && g_Player.IsTV) { pDlgOK.SetLine(1, Navigator.CurrentChannel); } else { pDlgOK.SetLine(1, ""); } pDlgOK.SetLine(2, GUILocalizeStrings.Get(1510)); //Connection to TV server lost pDlgOK.DoModal(GUIWindowManager.ActiveWindow); } public static void ShowDlgThread() { GUIWindow guiWindow = GUIWindowManager.GetWindow(GUIWindowManager.ActiveWindow); int count = 0; while (count < 50) { if (guiWindow.WindowLoaded) { break; } else { Thread.Sleep(100); } count++; } if (guiWindow.WindowLoaded) { ShowDlgAsynch(); } } private static void RefreshConnectionState() { IController iController = RemoteControl.Instance; //calling instance will make sure the state is refreshed. } public static bool HandleServerNotConnected() { // _doingHandleServerNotConnected is used to avoid multiple calls to this method. // the result could be that the dialogue is not shown. if (_ServerNotConnectedHandled) { return true; //still not connected } if (_doingHandleServerNotConnected) { return false; //we assume we are still not connected } _doingHandleServerNotConnected = true; try { if (!Connected) { //Card.User.Name = new User().Name; if (g_Player.Playing) { if (g_Player.IsTimeShifting) // live TV or radio must be stopped TVHome.StopPlayerMainThread(); else // playing something else so do not disturb return true; } if (g_Player.FullScreen) { GUIMessage initMsgTV = null; initMsgTV = new GUIMessage(GUIMessage.MessageType.GUI_MSG_WINDOW_INIT, (int)Window.WINDOW_TV, 0, 0, 0, 0, null); GUIWindowManager.SendThreadMessage(initMsgTV); return true; } Thread showDlgThread = new Thread(ShowDlgThread); showDlgThread.IsBackground = true; // show the dialog asynch. // this fixes a hang situation that would happen when resuming TV with showlastactivemodule showDlgThread.Start(); return true; } else { bool gentleConnected = WaitForGentleConnection(); if (!gentleConnected) { return true; } } } catch (Exception e) { //we assume that server is disconnected. Log.Error("TVHome.HandleServerNotConnected caused an error {0},{1}", e.Message, e.StackTrace); return true; } finally { _doingHandleServerNotConnected = false; } return false; } public static bool WaitForGentleConnection() { // lets try one more time - seems like the gentle framework is not properly initialized when coming out of standby/hibernation. // lets wait 10 secs before giving up. bool success = false; Stopwatch timer = Stopwatch.StartNew(); while (!success && timer.ElapsedMilliseconds < 10000) //10sec max { try { IList cards = TvDatabase.Card.ListAll(); success = true; } catch (Exception) { success = false; Log.Debug("TVHome: waiting for gentle.net DB connection {0} msec", timer.ElapsedMilliseconds); Thread.Sleep(100); } } if (!success) { RemoteControl.Clear(); GUIWindowManager.ActivateWindow((int)Window.WINDOW_SETTINGS_TVENGINE); //GUIWaitCursor.Hide(); } return success; } public static List PreferredLanguages { set { _preferredLanguages = value; } } public static bool PreferAC3 { set { _preferAC3 = value; } } public static bool PreferAudioTypeOverLang { set { _preferAudioTypeOverLang = value; } } public static bool UserChannelChanged { set { _userChannelChanged = value; } get { return _userChannelChanged; } } public static TVUtil Util { get { if (_util == null) { _util = new TVUtil(); } return _util; } } public static TvServer TvServer { get { if (_server == null) { _server = new TvServer(); } return _server; } } public static bool IsAnyCardRecording { get { return _isAnyCardRecording; } } public static bool Connected { get { return _connected; } set { _connected = value; } } public static VirtualCard Card { get { if (_card == null) { IUser user = new User(); _card = TvServer.CardByIndex(user, 0); } return _card; } set { if (_card != null) { _card = value; } } } #endregion #region Serialisation private static void LoadSettings() { if (settingsLoaded) { return; } using (Settings xmlreader = new MPSettings()) { m_navigator.LoadSettings(xmlreader); _autoTurnOnTv = xmlreader.GetValueAsBool("mytv", "autoturnontv", false); _showlastactivemodule = xmlreader.GetValueAsBool("general", "showlastactivemodule", false); _showlastactivemoduleFullscreen = xmlreader.GetValueAsBool("general", "lastactivemodulefullscreen", false); _waitonresume = xmlreader.GetValueAsInt("tvservice", "waitonresume", 0); string strValue = xmlreader.GetValueAsString("mytv", "defaultar", "Normal"); GUIGraphicsContext.ARType = Utils.GetAspectRatio(strValue); string preferredLanguages = xmlreader.GetValueAsString("tvservice", "preferredaudiolanguages", ""); _preferredLanguages = new List(); Log.Debug("TVHome.LoadSettings(): Preferred Audio Languages: " + preferredLanguages); StringTokenizer st = new StringTokenizer(preferredLanguages, ";"); while (st.HasMore) { string lang = st.NextToken(); if (lang.Length != 3) { Log.Warn("Language {0} is not in the correct format!", lang); } else { _preferredLanguages.Add(lang); Log.Info("Prefered language {0} is {1}", _preferredLanguages.Count, lang); } } _usertsp = xmlreader.GetValueAsBool("tvservice", "usertsp", !Network.IsSingleSeat()); _recordingpath = xmlreader.GetValueAsString("tvservice", "recordingpath", ""); _timeshiftingpath = xmlreader.GetValueAsString("tvservice", "timeshiftingpath", ""); _preferAC3 = xmlreader.GetValueAsBool("tvservice", "preferac3", false); _preferAudioTypeOverLang = xmlreader.GetValueAsBool("tvservice", "preferAudioTypeOverLang", true); _autoFullScreen = xmlreader.GetValueAsBool("mytv", "autofullscreen", false); _showChannelStateIcons = xmlreader.GetValueAsBool("mytv", "showChannelStateIcons", true); _notifyTVTimeout = xmlreader.GetValueAsInt("mytv", "notifyTVTimeout", 15); _playNotifyBeep = xmlreader.GetValueAsBool("mytv", "notifybeep", true); _preNotifyConfig = xmlreader.GetValueAsInt("mytv", "notifyTVBefore", 300); } settingsLoaded = true; } private static void SaveSettings() { if (m_navigator != null) { using (Settings xmlwriter = new MPSettings()) { m_navigator.SaveSettings(xmlwriter); } } } #endregion #region Private methods private static void SetRemoteControlHostName() { string hostName; using (Settings xmlreader = new MPSettings()) { hostName = xmlreader.GetValueAsString("tvservice", "hostname", ""); if (string.IsNullOrEmpty(hostName) || hostName == "localhost") { try { hostName = Dns.GetHostName(); Log.Info("TVHome: No valid hostname specified in mediaportal.xml!"); xmlreader.SetValue("tvservice", "hostname", hostName); hostName = "localhost"; Settings.SaveCache(); } catch (Exception ex) { Log.Info("TVHome: Error resolving hostname - {0}", ex.Message); return; } } } RemoteControl.HostName = hostName; Log.Info("Remote control:master server :{0}", RemoteControl.HostName); } private static void HandleWakeUpTvServer() { bool isWakeOnLanEnabled; bool isAutoMacAddressEnabled; int intTimeOut; String macAddress; byte[] hwAddress; using (Settings xmlreader = new MPSettings()) { isWakeOnLanEnabled = xmlreader.GetValueAsBool("tvservice", "isWakeOnLanEnabled", false); isAutoMacAddressEnabled = xmlreader.GetValueAsBool("tvservice", "isAutoMacAddressEnabled", false); intTimeOut = xmlreader.GetValueAsInt("tvservice", "WOLTimeOut", 10); } if (isWakeOnLanEnabled) { if (!Network.IsSingleSeat()) { WakeOnLanManager wakeOnLanManager = new WakeOnLanManager(); if (isAutoMacAddressEnabled) { IPAddress ipAddress = null; // Check if we already have a valid IP address stored in RemoteControl.HostName, // otherwise try to resolve the IP address if (!IPAddress.TryParse(RemoteControl.HostName, out ipAddress)) { // Get IP address of the TV server try { IPAddress[] ips; ips = Dns.GetHostAddresses(RemoteControl.HostName); Log.Debug("TVHome: WOL - GetHostAddresses({0}) returns:", RemoteControl.HostName); foreach (IPAddress ip in ips) { Log.Debug(" {0}", ip); } // Use first valid IP address ipAddress = ips[0]; } catch (Exception ex) { Log.Error("TVHome: WOL - Failed GetHostAddress - {0}", ex.Message); } } // Check for valid IP address if (ipAddress != null) { // Update the MAC address if possible hwAddress = wakeOnLanManager.GetHardwareAddress(ipAddress); if (wakeOnLanManager.IsValidEthernetAddress(hwAddress)) { Log.Debug("TVHome: WOL - Valid auto MAC address: {0:x}:{1:x}:{2:x}:{3:x}:{4:x}:{5:x}" , hwAddress[0], hwAddress[1], hwAddress[2], hwAddress[3], hwAddress[4], hwAddress[5]); // Store MAC address macAddress = BitConverter.ToString(hwAddress).Replace("-", ":"); Log.Debug("TVHome: WOL - Store MAC address: {0}", macAddress); using ( MediaPortal.Profile.Settings xmlwriter = new MediaPortal.Profile.MPSettings()) { xmlwriter.SetValue("tvservice", "macAddress", macAddress); } } } } // Use stored MAC address using (Settings xmlreader = new MPSettings()) { macAddress = xmlreader.GetValueAsString("tvservice", "macAddress", null); } Log.Debug("TVHome: WOL - Use stored MAC address: {0}", macAddress); try { hwAddress = wakeOnLanManager.GetHwAddrBytes(macAddress); // Finally, start up the TV server Log.Info("TVHome: WOL - Start the TV server"); if (wakeOnLanManager.WakeupSystem(hwAddress, RemoteControl.HostName, intTimeOut)) { Log.Info("TVHome: WOL - The TV server started successfully!"); } else { Log.Error("TVHome: WOL - Failed to start the TV server"); } } catch (Exception ex) { Log.Error("TVHome: WOL - Failed to start the TV server - {0}", ex.Message); } } } } ///// ///// Register the remoting service and attaching ciMenuHandler for server events ///// //public static void RegisterCiMenu(int newCardId) //{ // if (ciMenuHandler == null) // { // Log.Debug("CiMenu: PrepareCiMenu"); // ciMenuHandler = new CiMenuHandler(); // // opens remoting and attach local eventhandler to server event, call only once // RemoteControl.RegisterCiMenuCallbacks(ciMenuHandler); // } // // Check if card supports CI menu // if (newCardId != -1 && RemoteControl.Instance.CiMenuSupported(newCardId)) // { // // Enable CI menu handling in card // RemoteControl.Instance.SetCiMenuHandler(newCardId, null); // Log.Debug("TvPlugin: CiMenuHandler attached to new card {0}", newCardId); // } //} private static void RemoteControl_OnRemotingConnected() { if (!Connected) Log.Info("TVHome: OnRemotingConnected, recovered from a disconnection"); Connected = true; _ServerNotConnectedHandled = false; if (_recoverTV) { _recoverTV = false; GUIMessage initMsg = null; initMsg = new GUIMessage(GUIMessage.MessageType.GUI_MSG_WINDOW_INIT, (int)Window.WINDOW_TV_OVERLAY, 0, 0, 0, 0, null); GUIWindowManager.SendThreadMessage(initMsg); } } private static void RemoteControl_OnRemotingDisconnected() { if (Connected) Log.Info("TVHome: OnRemotingDisconnected"); Connected = false; HandleServerNotConnected(); } private void Application_ApplicationExit(object sender, EventArgs e) { try { if (Card.IsTimeShifting) { Card.User.Name = new User().Name; Card.StopTimeShifting(); } _notifyManager.Stop(); stopHeartBeatThread(); } catch (Exception) { } } private void HeartBeatTransmitter() { int countToHBLoop = 5; while (true) { // 1 second loop if (Connected) { _isAnyCardRecording = TvServer.IsAnyCardRecording(); } // HeartBeat loop (5 seconds) if (countToHBLoop >= 5) { countToHBLoop = 0; if (!Connected) // is this needed to update connection status RefreshConnectionState(); if (Connected && !_suspended) { bool isTS = (Card != null && Card.IsTimeShifting); if (Connected && isTS) { // send heartbeat to tv server each 5 sec. // this way we signal to the server that we are alive thus avoid being kicked. // Log.Debug("TVHome: sending HeartBeat signal to server."); // when debugging we want to disable heartbeats #if !DEBUG try { RemoteControl.Instance.HeartBeat(Card.User); } catch (Exception e) { Log.Error("TVHome: failed sending HeartBeat signal to server. ({0})", e.Message); } #endif } else if (Connected && !isTS && !_playbackStopped && _onPageLoadDone && (!g_Player.IsTVRecording && (g_Player.IsTV || g_Player.IsRadio))) { // check the possible reason why timeshifting has suddenly stopped // maybe the server kicked the client b/c a recording on another transponder was due. TvStoppedReason result = Card.GetTimeshiftStoppedReason; if (result != TvStoppedReason.UnknownReason) { Log.Debug("TVHome: Timeshifting seems to have stopped - TvStoppedReason:{0}", result); string errMsg = ""; switch (result) { case TvStoppedReason.HeartBeatTimeOut: errMsg = GUILocalizeStrings.Get(1515); break; case TvStoppedReason.KickedByAdmin: errMsg = GUILocalizeStrings.Get(1514); break; case TvStoppedReason.RecordingStarted: errMsg = GUILocalizeStrings.Get(1513); break; case TvStoppedReason.OwnerChangedTS: errMsg = GUILocalizeStrings.Get(1517); break; default: errMsg = GUILocalizeStrings.Get(1516); break; } NotifyUser(errMsg); } } } } Thread.Sleep(HEARTBEAT_INTERVAL * 1000); //sleep for 1 sec. before sending heartbeat again countToHBLoop++; } } /// /// Notify the user about the reason of stopped live tv. /// Ensures that the dialog is run in main thread. /// /// The error messages private static void NotifyUser(string errMsg) { // show dialogue only on main thread. if (GUIGraphicsContext.form.InvokeRequired) { ShowDlgMessageAsynchDelegate d = new ShowDlgMessageAsynchDelegate(NotifyUser); GUIGraphicsContext.form.Invoke(d, errMsg); return; } GUIDialogOK pDlgOK = (GUIDialogOK)GUIWindowManager.GetWindow((int)Window.WINDOW_DIALOG_OK); if (pDlgOK != null) { if (GUIWindowManager.ActiveWindow == (int)(int)Window.WINDOW_TVFULLSCREEN) { GUIWindowManager.ActivateWindow((int)Window.WINDOW_TV, true); } pDlgOK.SetHeading(GUILocalizeStrings.Get(605) + " - " + Navigator.CurrentChannel); //my tv errMsg = errMsg.Replace("\\r", "\r"); string[] lines = errMsg.Split('\r'); for (int i = 0; i < lines.Length; i++) { string line = lines[i]; pDlgOK.SetLine(1 + i, line); } pDlgOK.DoModal(GUIWindowManager.ActiveWindowEx); } Action keyAction = new Action(Action.ActionType.ACTION_STOP, 0, 0); GUIGraphicsContext.OnAction(keyAction); _playbackStopped = true; } private void startHeartBeatThread() { // setup heartbeat transmitter thread. // thread already running, then leave it. if (heartBeatTransmitterThread != null) { if (heartBeatTransmitterThread.IsAlive) { return; } } Log.Debug("TVHome: HeartBeat Transmitter started."); heartBeatTransmitterThread = new Thread(HeartBeatTransmitter); heartBeatTransmitterThread.IsBackground = true; heartBeatTransmitterThread.Name = "TvClient-TvHome: HeartBeat transmitter thread"; heartBeatTransmitterThread.Start(); } private void stopHeartBeatThread() { if (heartBeatTransmitterThread != null) { if (heartBeatTransmitterThread.IsAlive) { Log.Debug("TVHome: HeartBeat Transmitter stopped."); heartBeatTransmitterThread.Abort(); } } } #endregion public static void OnSelectGroup() { GUIDialogMenu dlg = (GUIDialogMenu)GUIWindowManager.GetWindow((int)Window.WINDOW_DIALOG_MENU); if (dlg == null) { return; } dlg.Reset(); dlg.SetHeading(971); // group int selected = 0; for (int i = 0; i < Navigator.Groups.Count; ++i) { dlg.Add(Navigator.Groups[i].GroupName); if (Navigator.Groups[i].GroupName == Navigator.CurrentGroup.GroupName) { selected = i; } } dlg.SelectedLabel = selected; dlg.DoModal(GUIWindowManager.ActiveWindow); if (dlg.SelectedLabel < 0) { return; } Navigator.SetCurrentGroup(dlg.SelectedLabelText); GUIPropertyManager.SetProperty("#TV.Guide.Group", dlg.SelectedLabelText); } private void OnSelectChannel() { Stopwatch benchClock = null; benchClock = Stopwatch.StartNew(); TvMiniGuide miniGuide = (TvMiniGuide)GUIWindowManager.GetWindow((int)Window.WINDOW_MINI_GUIDE); miniGuide.AutoZap = false; miniGuide.SelectedChannel = Navigator.Channel; miniGuide.DoModal(GetID); //Only change the channel if the channel selectd is actually different. //Without this, a ChannelChange might occur even when MiniGuide is canceled. if (!miniGuide.Canceled) { ViewChannelAndCheck(miniGuide.SelectedChannel); UpdateGUIonPlaybackStateChange(); } benchClock.Stop(); Log.Debug("TVHome.OnSelecChannel(): Total Time {0} ms", benchClock.ElapsedMilliseconds.ToString()); } private void TvDelayThread() { //we have to use a small delay before calling tvfullscreen. Thread.Sleep(200); // wait for timeshifting to complete int waits = 0; while (_playbackStopped && waits < 100) { //Log.Debug("TVHome.OnPageLoad(): waiting for timeshifting to start"); Thread.Sleep(100); waits++; } if (!_playbackStopped) { g_Player.ShowFullScreenWindow(); } } private void OnSuspend() { Log.Debug("TVHome.OnSuspend()"); RemoteControl.OnRemotingDisconnected -= new RemoteControl.RemotingDisconnectedDelegate(RemoteControl_OnRemotingDisconnected); RemoteControl.OnRemotingConnected -= new RemoteControl.RemotingConnectedDelegate(RemoteControl_OnRemotingConnected); try { if (Card.IsTimeShifting) { Card.User.Name = new User().Name; Card.StopTimeShifting(); } _notifyManager.Stop(); stopHeartBeatThread(); //Connected = false; _ServerNotConnectedHandled = false; } catch (Exception) { } finally { _ServerNotConnectedHandled = false; _suspended = true; } } private void OnResume() { Log.Debug("TVHome.OnResume()"); try { Connected = false; RemoteControl.OnRemotingDisconnected += new RemoteControl.RemotingDisconnectedDelegate(RemoteControl_OnRemotingDisconnected); RemoteControl.OnRemotingConnected += new RemoteControl.RemotingConnectedDelegate(RemoteControl_OnRemotingConnected); HandleWakeUpTvServer(); startHeartBeatThread(); _notifyManager.Start(); if (_resumeChannel != null) { Log.Debug("TVHome.OnResume() - automatically turning on TV: {0}", _resumeChannel.DisplayName); AutoTurnOnTv(_resumeChannel); AutoFullScreenTv(); _resumeChannel = null; } } finally { _suspended = false; } } public void Start() { Log.Debug("TVHome.Start()"); } public void Stop() { Log.Debug("TVHome.Stop()"); } public bool WndProc(ref Message msg) { if (msg.Msg == WM_POWERBROADCAST) { switch (msg.WParam.ToInt32()) { case PBT_APMSTANDBY: Log.Info("TVHome.WndProc(): Windows is going to standby"); OnSuspend(); break; case PBT_APMSUSPEND: Log.Info("TVHome.WndProc(): Windows is suspending"); OnSuspend(); break; case PBT_APMQUERYSUSPEND: case PBT_APMQUERYSTANDBY: Log.Info("TVHome.WndProc(): Windows is going into powerstate (hibernation/standby)"); break; case PBT_APMRESUMESUSPEND: Log.Info("TVHome.WndProc(): Windows has resumed from hibernate mode"); OnResume(); break; case PBT_APMRESUMESTANDBY: Log.Info("TVHome.WndProc(): Windows has resumed from standby mode"); OnResume(); break; } } return false; // false = all other processes will handle the msg } private static bool wasPrevWinTVplugin() { bool result = false; int act = GUIWindowManager.ActiveWindow; int prev = GUIWindowManager.GetWindow(act).PreviousWindowId; //plz add any newly added ID's to this list. result = ( prev == (int)Window.WINDOW_TV_CROP_SETTINGS || prev == (int)Window.WINDOW_SETTINGS_SORT_CHANNELS || prev == (int)Window.WINDOW_SETTINGS_TV_EPG || prev == (int)Window.WINDOW_TVFULLSCREEN || prev == (int)Window.WINDOW_TVGUIDE || prev == (int)Window.WINDOW_MINI_GUIDE || prev == (int)Window.WINDOW_TV_SEARCH || prev == (int)Window.WINDOW_TV_SEARCHTYPE || prev == (int)Window.WINDOW_TV_SCHEDULER_PRIORITIES || prev == (int)Window.WINDOW_TV_PROGRAM_INFO || prev == (int)Window.WINDOW_RECORDEDTV || prev == (int)Window.WINDOW_TV_RECORDED_INFO || prev == (int)Window.WINDOW_SETTINGS_RECORDINGS || prev == (int)Window.WINDOW_SCHEDULER || prev == (int)Window.WINDOW_SEARCHTV || prev == (int)Window.WINDOW_TV_TUNING_DETAILS || prev == (int)Window.WINDOW_TV ); if (!result && prev == (int)Window.WINDOW_FULLSCREEN_VIDEO && g_Player.IsTVRecording) { result = true; } return result; } public static void OnGlobalMessage(GUIMessage message) { switch (message.Message) { case GUIMessage.MessageType.GUI_MSG_STOP_SERVER_TIMESHIFTING: { User user = new User(); if (user.Name == Card.User.Name) { Card.StopTimeShifting(); } ; break; } case GUIMessage.MessageType.GUI_MSG_NOTIFY_REC: string heading = message.Label; string text = message.Label2; Channel ch = message.Object as Channel; //Log.Debug("Received rec notify message: {0}, {1}, {2}", heading, text, (ch != null).ToString()); //remove later string logo = TVUtil.GetChannelLogo(ch); GUIDialogNotify pDlgNotify = (GUIDialogNotify)GUIWindowManager.GetWindow((int)GUIWindow.Window.WINDOW_DIALOG_NOTIFY); if (pDlgNotify != null) { pDlgNotify.Reset(); pDlgNotify.ClearAll(); pDlgNotify.SetHeading(heading); if (!string.IsNullOrEmpty(text)) { pDlgNotify.SetText(text); } pDlgNotify.SetImage(logo); pDlgNotify.TimeOut = 5; pDlgNotify.DoModal(GUIWindowManager.ActiveWindow); } break; case GUIMessage.MessageType.GUI_MSG_NOTIFY_TV_PROGRAM: { TVNotifyYesNoDialog tvNotifyDlg = (TVNotifyYesNoDialog)GUIWindowManager.GetWindow((int)GUIWindow.Window.WINDOW_DIALOG_TVNOTIFYYESNO); TVProgramDescription notify = message.Object as TVProgramDescription; if (tvNotifyDlg == null || notify == null) { return; } int minUntilStart = _preNotifyConfig / 60; if (notify.StartTime > DateTime.Now) { if (minUntilStart > 1) { tvNotifyDlg.SetHeading(String.Format(GUILocalizeStrings.Get(1018), minUntilStart)); } else { tvNotifyDlg.SetHeading(1019); // Program is about to begin } } else { tvNotifyDlg.SetHeading(String.Format(GUILocalizeStrings.Get(1206), (DateTime.Now - notify.StartTime).Minutes.ToString())); } tvNotifyDlg.SetLine(1, notify.Title); tvNotifyDlg.SetLine(2, notify.Description); tvNotifyDlg.SetLine(4, String.Format(GUILocalizeStrings.Get(1207), notify.Channel.DisplayName)); Channel c = notify.Channel; string strLogo = string.Empty; if (c.IsTv) { strLogo = MediaPortal.Util.Utils.GetCoverArt(Thumbs.TVChannel, c.DisplayName); } else if (c.IsRadio) { strLogo = MediaPortal.Util.Utils.GetCoverArt(Thumbs.Radio, c.DisplayName); } tvNotifyDlg.SetImage(strLogo); tvNotifyDlg.TimeOut = _notifyTVTimeout; if (_playNotifyBeep) { MediaPortal.Util.Utils.PlaySound("notify.wav", false, true); } tvNotifyDlg.SetDefaultToYes(false); tvNotifyDlg.DoModal(GUIWindowManager.ActiveWindow); if (tvNotifyDlg.IsConfirmed) { try { MediaPortal.Player.g_Player.Stop(); if (c.IsTv) { MediaPortal.GUI.Library.GUIWindowManager.ActivateWindow((int)MediaPortal.GUI.Library.GUIWindow.Window.WINDOW_TV); TVHome.ViewChannelAndCheck(c); if (TVHome.Card.IsTimeShifting && TVHome.Card.IdChannel == c.IdChannel) { g_Player.ShowFullScreenWindow(); } } else if (c.IsRadio) { MediaPortal.GUI.Library.GUIWindowManager.ActivateWindow((int)MediaPortal.GUI.Library.GUIWindow.Window.WINDOW_RADIO); Radio.CurrentChannel = c; Radio.Play(); } } catch (Exception e) { Log.Error("TVHome: TVNotification: Error on starting channel {0} after notification: {1} {2} {3}", notify.Channel.DisplayName, e.Message, e.Source, e.StackTrace); } } break; } } } private void OnAudioTracksReady() { Log.Debug("TVHome.OnAudioTracksReady()"); eAudioDualMonoMode dualMonoMode = eAudioDualMonoMode.UNSUPPORTED; int prefLangIdx = GetPreferedAudioStreamIndex(out dualMonoMode); g_Player.CurrentAudioStream = prefLangIdx; if (dualMonoMode != eAudioDualMonoMode.UNSUPPORTED) { g_Player.SetAudioDualMonoMode(dualMonoMode); } else if (g_Player.GetAudioDualMonoMode() != eAudioDualMonoMode.UNSUPPORTED) { g_Player.SetAudioDualMonoMode(eAudioDualMonoMode.STEREO); } } private void OnPlayBackStarted(g_Player.MediaType type, string filename) { // when we are watching TV and suddenly decides to watch a audio/video etc., we want to make sure that the TV is stopped on server. GUIWindow currentWindow = GUIWindowManager.GetWindow(GUIWindowManager.ActiveWindow); if (type == g_Player.MediaType.Radio || type == g_Player.MediaType.TV) { UpdateGUIonPlaybackStateChange(true); } if (currentWindow.IsTv && type == g_Player.MediaType.TV) { return; } if (GUIWindowManager.ActiveWindow == (int)Window.WINDOW_RADIO || GUIWindowManager.ActiveWindow == (int)Window.WINDOW_RADIO_GUIDE) { return; } //gemx: fix for 0001181: Videoplayback does not work if tvservice.exe is not running bool isTS = (Card != null && Card.IsTimeShifting); if (Connected && isTS) { Card.StopTimeShifting(); } } private void OnPlayBackStopped(g_Player.MediaType type, int stoptime, string filename) { if (type != g_Player.MediaType.TV && type != g_Player.MediaType.Radio) { return; } StopPlayback(); UpdateGUIonPlaybackStateChange(false); } private static void StopPlayback() { //gemx: fix for 0001181: Videoplayback does not work if tvservice.exe is not running if (!Connected) { _recoverTV = true; return; } if (Card.IsTimeShifting == false) { return; } //tv off Log.Info("TVHome:turn tv off"); SaveSettings(); Card.User.Name = new User().Name; Card.StopTimeShifting(); _recoverTV = false; _playbackStopped = true; } public static bool ManualRecord(Channel channel, int dialogId) { if (GUIWindowManager.ActiveWindowEx == (int)(int)Window.WINDOW_TVFULLSCREEN) { Log.Info("send message to fullscreen tv"); GUIMessage msg = new GUIMessage(GUIMessage.MessageType.GUI_MSG_RECORD, GUIWindowManager.ActiveWindow, 0, 0, 0, 0, null); msg.SendToTargetWindow = true; msg.TargetWindowId = (int)(int)Window.WINDOW_TVFULLSCREEN; GUIGraphicsContext.SendMessage(msg); return false; } Log.Info("TVHome:Record action"); var server = new TvServer(); VirtualCard card = null; Program prog = channel.CurrentProgram; bool isRecording; bool hasProgram = (prog != null); if (hasProgram) { prog.Refresh();//refresh the states from db isRecording = (prog.IsRecording || prog.IsRecordingOncePending); } else { isRecording = server.IsRecording(channel.IdChannel, out card); } if (!isRecording) { if (hasProgram) { GUIDialogMenuBottomRight pDlgOK = (GUIDialogMenuBottomRight)GUIWindowManager.GetWindow((int)Window.WINDOW_DIALOG_MENU_BOTTOM_RIGHT); if (pDlgOK != null) { pDlgOK.Reset(); pDlgOK.SetHeading(605); //my tv pDlgOK.AddLocalizedString(875); //current program pDlgOK.AddLocalizedString(876); //till manual stop pDlgOK.DoModal(GUIWindowManager.ActiveWindow); switch (pDlgOK.SelectedId) { case 875: //record current program TVProgramInfo.CreateProgram(prog, (int)ScheduleRecordingType.Once, dialogId); return true; case 876: //manual bool doesManuelScheduleAlreadyExist = DoesManualScheduleAlreadyExist(channel); if (!doesManuelScheduleAlreadyExist) { StartRecordingSchedule(channel, true); return true; } break; } } } else { //manual record StartRecordingSchedule(channel, true); return true; } } else { Schedule s = null; int idChannel = 0; if (hasProgram) { TVProgramInfo.IsRecordingProgram(prog, out s, false); if (s != null) { idChannel = s.ReferencedChannel().IdChannel; } } else { s = Schedule.Retrieve(card.RecordingScheduleId); idChannel = card.IdChannel; } if (s != null && idChannel > 0) { TVUtil.DeleteRecAndSchedWithPrompt(s, idChannel); } } return false; } private static bool DoesManualScheduleAlreadyExist(Channel channel) { Schedule existingSchedule = Schedule.FindNoEPGSchedule(channel); return (existingSchedule != null); } private void UpdateGUIonPlaybackStateChange(bool playbackStarted) { if (btnTvOnOff.Selected != playbackStarted) { btnTvOnOff.Selected = playbackStarted; } UpdateProgressPercentageBar(); bool hasTeletext = (!Connected || Card.HasTeletext) && (playbackStarted); btnTeletext.IsVisible = hasTeletext; } private void UpdateGUIonPlaybackStateChange() { bool isTimeShiftingTV = (Connected && Card.IsTimeShifting && g_Player.IsTV); if (btnTvOnOff.Selected != isTimeShiftingTV) { btnTvOnOff.Selected = isTimeShiftingTV; } UpdateProgressPercentageBar(); bool hasTeletext = (!Connected || Card.HasTeletext) && (isTimeShiftingTV); btnTeletext.IsVisible = hasTeletext; } private void UpdateCurrentChannel() { if (!g_Player.Playing) { return; } Navigator.UpdateCurrentChannel(); UpdateProgressPercentageBar(); GUIControl.HideControl(GetID, (int)Controls.LABEL_REC_INFO); GUIControl.HideControl(GetID, (int)Controls.IMG_REC_RECTANGLE); GUIControl.HideControl(GetID, (int)Controls.IMG_REC_CHANNEL); } /// /// This function replaces g_player.ShowFullScreenWindowTV /// /// private static bool ShowFullScreenWindowTVHandler() { if ((g_Player.IsTV && Card.IsTimeShifting) || g_Player.IsTVRecording) { //push chapter and jumppoint information into the gui property manager if (g_Player.IsTVRecording && g_Player.HasChapters) { double[] chapters = g_Player.Chapters; double[] jumppoints = g_Player.JumpPoints; string strChapters = string.Empty; string strJumpPoints = string.Empty; double duration = g_Player.Duration; if (chapters != null) { foreach(double chapter in chapters) { double chapterPercent = chapter/duration*100.0d; strChapters += String.Format("{0:0.00}", chapterPercent) + " "; } } if (jumppoints != null) { foreach (double jump in jumppoints) { double jumpPercent = jump/duration*100.0d; strJumpPoints += String.Format("{0:0.00}", jumpPercent) + " "; } } GUIPropertyManager.SetProperty("#TV.Record.chapters", strChapters); GUIPropertyManager.SetProperty("#TV.Record.jumppoints", strJumpPoints); Log.Debug("TVHome.ShowFullScreenWindowTVHandler - setting chapters: " + strChapters); Log.Debug("TVHome.ShowFUllScreenWindowTVHandler - setting jumppoints: " + strJumpPoints); } else { GUIPropertyManager.SetProperty("#TV.Record.chapters", string.Empty); GUIPropertyManager.SetProperty("#TV.Record.jumppoints", string.Empty); } // watching TV if (GUIWindowManager.ActiveWindow == (int)Window.WINDOW_TVFULLSCREEN) { return true; } Log.Info("TVHome: ShowFullScreenWindow switching to fullscreen tv"); GUIWindowManager.ActivateWindow((int)Window.WINDOW_TVFULLSCREEN); GUIGraphicsContext.IsFullScreenVideo = true; return true; } return g_Player.ShowFullScreenWindowTVDefault(); } public static void UpdateTimeShift() { } private void OnActiveRecordings() { IList ignoreActiveRecordings = new List(); OnActiveRecordings(ignoreActiveRecordings); } private void OnActiveRecordings(IList ignoreActiveRecordings) { GUIDialogMenu dlg = (GUIDialogMenu)GUIWindowManager.GetWindow((int)Window.WINDOW_DIALOG_MENU); if (dlg == null) { return; } dlg.Reset(); dlg.SetHeading(200052); // Active Recordings IList activeRecordings = Recording.ListAllActive(); if (activeRecordings != null && activeRecordings.Count > 0) { foreach (Recording activeRecording in activeRecordings) { if (!ignoreActiveRecordings.Contains(activeRecording)) { GUIListItem item = new GUIListItem(); string channelName = activeRecording.ReferencedChannel().DisplayName; string programTitle = activeRecording.Title.Trim(); // default is current EPG info item.Label = channelName; item.Label2 = programTitle; string strLogo = Utils.GetCoverArt(Thumbs.TVChannel, channelName); if (string.IsNullOrEmpty(strLogo)) { strLogo = "defaultVideoBig.png"; } item.IconImage = strLogo; item.IconImageBig = strLogo; item.PinImage = ""; dlg.Add(item); } } dlg.SelectedLabel = activeRecordings.Count; dlg.DoModal(this.GetID); if (dlg.SelectedLabel < 0) { return; } if (dlg.SelectedLabel < 0 || (dlg.SelectedLabel - 1 > activeRecordings.Count)) { return; } Recording selectedRecording = activeRecordings[dlg.SelectedLabel]; Schedule parentSchedule = selectedRecording.ReferencedSchedule(); if (parentSchedule == null || parentSchedule.IdSchedule < 1) { return; } bool deleted = TVUtil.StopRecAndSchedWithPrompt(parentSchedule, selectedRecording.IdChannel); if (deleted && !ignoreActiveRecordings.Contains(selectedRecording)) { ignoreActiveRecordings.Add(selectedRecording); } OnActiveRecordings(ignoreActiveRecordings); //keep on showing the list until --> 1) user leaves menu, 2) no more active recordings } else { GUIDialogOK pDlgOK = (GUIDialogOK)GUIWindowManager.GetWindow((int)Window.WINDOW_DIALOG_OK); if (pDlgOK != null) { pDlgOK.SetHeading(200052); //my tv pDlgOK.SetLine(1, GUILocalizeStrings.Get(200053)); // No Active recordings pDlgOK.SetLine(2, ""); pDlgOK.DoModal(this.GetID); } } } private void OnActiveStreams() { GUIDialogMenu dlg = (GUIDialogMenu)GUIWindowManager.GetWindow((int)Window.WINDOW_DIALOG_MENU); if (dlg == null) { return; } dlg.Reset(); dlg.SetHeading(692); // Active Tv Streams int selected = 0; IList cards = TvDatabase.Card.ListAll(); List channels = new List(); int count = 0; TvServer server = new TvServer(); List _users = new List(); foreach (Card card in cards) { if (card.Enabled == false) { continue; } if (!RemoteControl.Instance.CardPresent(card.IdCard)) { continue; } IUser[] users = RemoteControl.Instance.GetUsersForCard(card.IdCard); if (users == null) { return; } for (int i = 0; i < users.Length; ++i) { IUser user = users[i]; if (card.IdCard != user.CardId) { continue; } bool isRecording; bool isTimeShifting; VirtualCard tvcard = new VirtualCard(user, RemoteControl.HostName); isRecording = tvcard.IsRecording; isTimeShifting = tvcard.IsTimeShifting; if (isTimeShifting || (isRecording && !isTimeShifting)) { int idChannel = tvcard.IdChannel; user = tvcard.User; Channel ch = Channel.Retrieve(idChannel); channels.Add(ch); GUIListItem item = new GUIListItem(); item.Label = ch.DisplayName; item.Label2 = user.Name; string strLogo = Utils.GetCoverArt(Thumbs.TVChannel, ch.DisplayName); if (string.IsNullOrEmpty(strLogo)) { strLogo = "defaultVideoBig.png"; } item.IconImage = strLogo; if (isRecording) { item.PinImage = Thumbs.TvRecordingIcon; } else { item.PinImage = ""; } dlg.Add(item); _users.Add(user); if (Card != null && Card.IdChannel == idChannel) { selected = count; } count++; } } } if (channels.Count == 0) { GUIDialogOK pDlgOK = (GUIDialogOK)GUIWindowManager.GetWindow((int)Window.WINDOW_DIALOG_OK); if (pDlgOK != null) { pDlgOK.SetHeading(692); //my tv pDlgOK.SetLine(1, GUILocalizeStrings.Get(1511)); // No Active streams pDlgOK.SetLine(2, ""); pDlgOK.DoModal(this.GetID); } return; } dlg.SelectedLabel = selected; dlg.DoModal(this.GetID); if (dlg.SelectedLabel < 0) { return; } VirtualCard vCard = new VirtualCard(_users[dlg.SelectedLabel], RemoteControl.HostName); Channel channel = Navigator.GetChannel(vCard.IdChannel); ViewChannel(channel); } private void OnRecord() { ManualRecord(Navigator.Channel, GetID); UpdateStateOfRecButton(); } /// /// Update the state of the following buttons /// - record now /// private void UpdateStateOfRecButton() { if (!Connected) { btnTvOnOff.Selected = false; return; } bool isTimeShifting = Card.IsTimeShifting; //are we recording a tv program? if (Navigator.Channel != null && Card != null) { string label; TvServer server = new TvServer(); VirtualCard vc; if (server.IsRecording(Navigator.Channel.IdChannel, out vc)) { if (!isTimeShifting) { Card = vc; } //yes then disable the timeshifting on/off buttons //and change the Record Now button into Stop Record label = GUILocalizeStrings.Get(629); //stop record } else { //nop. then change the Record Now button //to Record Now label = GUILocalizeStrings.Get(601); // record } if (label != btnRecord.Label) { btnRecord.Label = label; } } } private void UpdateRecordingIndicator() { DateTime now = DateTime.Now; //Log.Debug("updaterec: conn:{0}, rec:{1}", Connected, Card.IsRecording); // if we're recording tv, update gui with info if (Connected && Card.IsRecording) { int scheduleId = Card.RecordingScheduleId; if (scheduleId > 0) { Schedule schedule = Schedule.Retrieve(scheduleId); if (schedule != null) { if (schedule.ScheduleType == (int)ScheduleRecordingType.Once) { imgRecordingIcon.SetFileName(Thumbs.TvRecordingIcon); } else { imgRecordingIcon.SetFileName(Thumbs.TvRecordingSeriesIcon); } } } } else { imgRecordingIcon.IsVisible = false; } } /// /// Update the the progressbar in the GUI which shows /// how much of the current tv program has elapsed /// public static void UpdateProgressPercentageBar() { TimeSpan ts = DateTime.Now - _updateProgressTimer; if (ts.TotalMilliseconds < PROGRESS_PERCENTAGE_UPDATE_INTERVAL) { return; } try { if (!Connected) { return; } //set audio video related media info properties. int currAudio = g_Player.CurrentAudioStream; if (currAudio > -1) { UpdateAudioProperties(currAudio); } // Check for recordings vs liveTv/Radio or Idle if (g_Player.IsTVRecording) { UpdateRecordingProperties(); } else { UpdateTvProperties(); } } finally { _updateProgressTimer = DateTime.Now; } } private static void UpdateTvProperties() { // No channel -> no EPG if (Navigator.Channel != null && !g_Player.IsRadio) { Channel infoChannel; if (Navigator.Channel.IsTv) { infoChannel = Navigator.Channel; } else { infoChannel = _lastTvChannel; } UpdateCurrentEpgProperties(infoChannel); UpdateNextEpgProperties(infoChannel); //Update lastTvChannel with current _lastTvChannel = infoChannel; } } private static void UpdateRecordingProperties() { double currentPosition = g_Player.CurrentPosition; double duration = g_Player.Duration; string startTime = Utils.SecondsToHMSString((int)currentPosition); string endTime = Utils.SecondsToHMSString((int)duration); double percentLivePoint = currentPosition / duration; percentLivePoint *= 100.0d; GUIPropertyManager.SetProperty("#TV.Record.percent1", percentLivePoint.ToString()); GUIPropertyManager.SetProperty("#TV.Record.percent2", "0"); GUIPropertyManager.SetProperty("#TV.Record.percent3", "0"); GUIPropertyManager.SetProperty("#TV.View.idProgram", 0); Recording rec = TvRecorded.ActiveRecording(); if (rec != null) { GUIPropertyManager.SetProperty("#TV.View.title", rec.Title); GUIPropertyManager.SetProperty("#TV.View.idProgram", 0); GUIPropertyManager.SetProperty("#TV.View.compositetitle", TVUtil.GetDisplayTitle(rec)); GUIPropertyManager.SetProperty("#TV.View.subtitle", rec.EpisodeName); GUIPropertyManager.SetProperty("#TV.View.episode", rec.EpisodeNumber); } else { GUIPropertyManager.SetProperty("#TV.View.title", g_Player.currentTitle); GUIPropertyManager.SetProperty("#TV.View.compositetitle", g_Player.currentTitle); } string displayName = TvRecorded.GetRecordingDisplayName(rec); GUIPropertyManager.SetProperty("#TV.View.channel", displayName + " (" + GUILocalizeStrings.Get(604) + ")"); GUIPropertyManager.SetProperty("#TV.View.description", g_Player.currentDescription); GUIPropertyManager.SetProperty("#TV.View.start", startTime); GUIPropertyManager.SetProperty("#TV.View.stop", endTime); string strLogo = Utils.GetCoverArt(Thumbs.TVChannel, displayName); GUIPropertyManager.SetProperty("#TV.View.thumb", string.IsNullOrEmpty(strLogo) ? "defaultVideoBig.png" : strLogo); } private static void UpdateAudioProperties(int currAudio) { string streamType = g_Player.AudioType(currAudio); GUIPropertyManager.SetProperty("#TV.View.IsAC3", string.Empty); GUIPropertyManager.SetProperty("#TV.View.IsMP1A", string.Empty); GUIPropertyManager.SetProperty("#TV.View.IsMP2A", string.Empty); GUIPropertyManager.SetProperty("#TV.View.IsAAC", string.Empty); GUIPropertyManager.SetProperty("#TV.View.IsLATMAAC", string.Empty); switch (streamType) { case "AC3": case "AC3plus": // just for the time being use the same icon for AC3 & AC3plus GUIPropertyManager.SetProperty("#TV.View.IsAC3", string.Format("{0}{1}{2}", GUIGraphicsContext.Skin, @"\Media\Logos\", "ac3.png")); break; case "Mpeg1": GUIPropertyManager.SetProperty("#TV.View.IsMP1A", string.Format("{0}{1}{2}", GUIGraphicsContext.Skin, @"\Media\Logos\", "mp1a.png")); break; case "Mpeg2": GUIPropertyManager.SetProperty("#TV.View.IsMP2A", string.Format("{0}{1}{2}", GUIGraphicsContext.Skin, @"\Media\Logos\", "mp2a.png")); break; case "AAC": GUIPropertyManager.SetProperty("#TV.View.IsAAC", string.Format("{0}{1}{2}", GUIGraphicsContext.Skin, @"\Media\Logos\", "aac.png")); break; case "LATMAAC": GUIPropertyManager.SetProperty("#TV.View.IsLATMAAC", string.Format("{0}{1}{2}", GUIGraphicsContext.Skin, @"\Media\Logos\", "latmaac3.png")); break; } } private static void UpdateCurrentEpgProperties(Channel ch) { bool hasChannel = (ch != null); Program current = null; if (hasChannel) { current = ch.CurrentProgram; } bool hasCurrentEPG = hasChannel && current != null; if (!hasChannel || !hasCurrentEPG) { ResetTvProperties(); if (!hasChannel) { GUIPropertyManager.SetProperty("#TV.View.channel", String.Empty); GUIPropertyManager.SetProperty("#TV.View.thumb", String.Empty); Log.Debug("UpdateCurrentEpgProperties: no channel, returning"); } else { GUIPropertyManager.SetProperty("#TV.View.channel", ch.DisplayName); SetTvThumbProperty(ch); } } else { GUIPropertyManager.SetProperty("#TV.View.channel", ch.DisplayName); GUIPropertyManager.SetProperty("#TV.View.title", current.Title); GUIPropertyManager.SetProperty("#TV.View.idProgram", current.idProgram); GUIPropertyManager.SetProperty("#TV.View.compositetitle", TVUtil.GetDisplayTitle(current)); GUIPropertyManager.SetProperty("#TV.View.start", current.StartTime.ToString("t", CultureInfo.CurrentCulture.DateTimeFormat)); GUIPropertyManager.SetProperty("#TV.View.stop", current.EndTime.ToString("t", CultureInfo.CurrentCulture.DateTimeFormat)); GUIPropertyManager.SetProperty("#TV.View.description", current.Description); GUIPropertyManager.SetProperty("#TV.View.subtitle", current.EpisodeName); GUIPropertyManager.SetProperty("#TV.View.episode", current.EpisodeNumber); GUIPropertyManager.SetProperty("#TV.View.genre", current.Genre); GUIPropertyManager.SetProperty("#TV.View.remaining", Utils.SecondsToHMSString(current.EndTime - current.StartTime)); SetTvThumbProperty(ch); TimeSpan ts = current.EndTime - current.StartTime; if (ts.TotalSeconds > 0) { // calculate total duration of the current program double programDuration = ts.TotalSeconds; //calculate where the program is at this time ts = (DateTime.Now - current.StartTime); double livePoint = ts.TotalSeconds; //calculate when timeshifting was started double timeShiftStartPoint = livePoint - g_Player.Duration; double playingPoint = timeShiftStartPoint + g_Player.CurrentPosition; if (timeShiftStartPoint < 0) { timeShiftStartPoint = 0; } double timeShiftStartPointPercent = timeShiftStartPoint / programDuration; timeShiftStartPointPercent *= 100.0d; GUIPropertyManager.SetProperty("#TV.Record.percent1", timeShiftStartPointPercent.ToString()); double playingPointPercent = playingPoint / programDuration; playingPointPercent *= 100.0d; GUIPropertyManager.SetProperty("#TV.Record.percent2", playingPointPercent.ToString()); double percentLivePoint = livePoint / programDuration; percentLivePoint *= 100.0d; GUIPropertyManager.SetProperty("#TV.View.Percentage", percentLivePoint.ToString()); GUIPropertyManager.SetProperty("#TV.Record.percent3", percentLivePoint.ToString()); GUIPropertyManager.SetProperty("#TV.Record.chapters", string.Empty); GUIPropertyManager.SetProperty("#TV.Record.jumppoints", string.Empty); } } } private static void SetTvThumbProperty(Channel ch) { string strLogo = Utils.GetCoverArt(Thumbs.TVChannel, ch.DisplayName); if (string.IsNullOrEmpty(strLogo)) { strLogo = "defaultVideoBig.png"; } GUIPropertyManager.SetProperty("#TV.View.thumb", strLogo); } private static void ResetTvProperties() { GUIPropertyManager.SetProperty("#TV.View.title", GUILocalizeStrings.Get(736)); // no epg for this channel GUIPropertyManager.SetProperty("#TV.View.compositetitle", GUILocalizeStrings.Get(736)); // no epg for this channel GUIPropertyManager.SetProperty("#TV.View.idProgram", 0); GUIPropertyManager.SetProperty("#TV.View.start", String.Empty); GUIPropertyManager.SetProperty("#TV.View.stop", String.Empty); GUIPropertyManager.SetProperty("#TV.View.description", String.Empty); GUIPropertyManager.SetProperty("#TV.View.subtitle", String.Empty); GUIPropertyManager.SetProperty("#TV.View.episode", String.Empty); GUIPropertyManager.SetProperty("#TV.View.genre", String.Empty); GUIPropertyManager.SetProperty("#TV.View.Percentage", "0"); GUIPropertyManager.SetProperty("#TV.Record.percent1", "0"); GUIPropertyManager.SetProperty("#TV.Record.percent2", "0"); GUIPropertyManager.SetProperty("#TV.Record.percent3", "0"); GUIPropertyManager.SetProperty("#TV.View.remaining", String.Empty); GUIPropertyManager.SetProperty("#TV.Record.chapters", string.Empty); GUIPropertyManager.SetProperty("#TV.Record.jumppoints", string.Empty); } private static void UpdateNextEpgProperties(Channel ch) { Program next = null; if (ch == null) { Log.Debug("UpdateNextEpgProperties: no channel, returning"); } else { next = ch.NextProgram; if (next == null) { Log.Debug("UpdateNextEpgProperties: no EPG data, returning"); } } if (next != null) { GUIPropertyManager.SetProperty("#TV.Next.title", next.Title); GUIPropertyManager.SetProperty("#TV.Next.idProgram", next.idProgram); GUIPropertyManager.SetProperty("#TV.Next.compositetitle", TVUtil.GetDisplayTitle(next)); GUIPropertyManager.SetProperty("#TV.Next.start", next.StartTime.ToString("t", CultureInfo.CurrentCulture.DateTimeFormat)); GUIPropertyManager.SetProperty("#TV.Next.stop", next.EndTime.ToString("t", CultureInfo.CurrentCulture.DateTimeFormat)); GUIPropertyManager.SetProperty("#TV.Next.description", next.Description); GUIPropertyManager.SetProperty("#TV.Next.subtitle", next.EpisodeName); GUIPropertyManager.SetProperty("#TV.Next.episode", next.EpisodeNumber); GUIPropertyManager.SetProperty("#TV.Next.genre", next.Genre); GUIPropertyManager.SetProperty("#TV.Next.remaining", Utils.SecondsToHMSString(next.EndTime - next.StartTime)); } else { GUIPropertyManager.SetProperty("#TV.Next.title", GUILocalizeStrings.Get(736)); // no epg for this channel GUIPropertyManager.SetProperty("#TV.Next.compositetitle", GUILocalizeStrings.Get(736)); // no epg for this channel GUIPropertyManager.SetProperty("#TV.Next.idProgram", 0); GUIPropertyManager.SetProperty("#TV.Next.start", String.Empty); GUIPropertyManager.SetProperty("#TV.Next.stop", String.Empty); GUIPropertyManager.SetProperty("#TV.Next.description", String.Empty); GUIPropertyManager.SetProperty("#TV.Next.subtitle", String.Empty); GUIPropertyManager.SetProperty("#TV.Next.episode", String.Empty); GUIPropertyManager.SetProperty("#TV.Next.genre", String.Empty); } } /// /// When called this method will switch to the previous TV channel /// public static void OnPreviousChannel() { Log.Info("TVHome:OnPreviousChannel()"); if (GUIGraphicsContext.IsFullScreenVideo) { // where in fullscreen so delayzap channel instead of immediatly tune.. TvFullScreen TVWindow = (TvFullScreen)GUIWindowManager.GetWindow((int)(int)Window.WINDOW_TVFULLSCREEN); if (TVWindow != null) { TVWindow.ZapPreviousChannel(); } return; } // Zap to previous channel immediately Navigator.ZapToPreviousChannel(false); } #region audio selection section /// /// unit test enabled method. please respect this. /// run and/or modify the unit tests accordingly. /// public static int GetPreferedAudioStreamIndex(out eAudioDualMonoMode dualMonoMode) // also used from tvrecorded class { int idxFirstAc3 = -1; // the index of the first avail. ac3 found int idxFirstmpeg = -1; // the index of the first avail. mpg found int idxStreamIndexAc3 = -1; // the streamindex of ac3 found based on lang. pref int idxStreamIndexmpeg = -1; // the streamindex of mpg found based on lang. pref int idx = -1; // the chosen audio index we return int idxLangPriAc3 = -1; // the lang priority of ac3 found based on lang. pref int idxLangPrimpeg = -1; // the lang priority of mpg found based on lang. pref string ac3BasedOnLang = ""; // for debugging, what lang. in prefs. where used to choose the ac3 audio track ? string mpegBasedOnLang = ""; // for debugging, what lang. in prefs. where used to choose the mpeg audio track ? dualMonoMode = eAudioDualMonoMode.UNSUPPORTED; IAudioStream[] streams = GetStreamsList(); if (IsPreferredAudioLanguageAvailable()) { Log.Debug( "TVHome.GetPreferedAudioStreamIndex(): preferred LANG(s):{0} preferAC3:{1} preferAudioTypeOverLang:{2}", String.Join(";", _preferredLanguages.ToArray()), _preferAC3, _preferAudioTypeOverLang); } else { Log.Debug( "TVHome.GetPreferedAudioStreamIndex(): preferred LANG(s):{0} preferAC3:{1} _preferAudioTypeOverLang:{2}", "n/a", _preferAC3, _preferAudioTypeOverLang); } Log.Debug("Audio streams avail: {0}", streams.Length); bool dualMonoModeEnabled = (g_Player.GetAudioDualMonoMode() != eAudioDualMonoMode.UNSUPPORTED); if (streams.Length == 1 && !ShouldApplyDualMonoMode(streams[0].Language)) { Log.Info("Audio stream: switching to preferred AC3/MPEG audio stream 0 (only 1 track avail.)"); return 0; } int priority = int.MaxValue; idxFirstAc3 = GetFirstAC3Index(streams); idxFirstmpeg = GetFirstMpegIndex(streams); UpdateAudioStreamIndexesAndPrioritiesBasedOnLanguage(streams, priority, ref idxStreamIndexmpeg, ref mpegBasedOnLang, ref idxStreamIndexAc3, idxLangPriAc3, idxLangPrimpeg, ref ac3BasedOnLang, out dualMonoMode); idx = GetAC3AudioStreamIndex(idxStreamIndexmpeg, idxStreamIndexAc3, ac3BasedOnLang, idx, idxFirstAc3); if (idx == -1 && _preferAC3) { Log.Info("Audio stream: no preferred AC3 audio stream found, trying mpeg instead."); } if (idx == -1 || !_preferAC3) // we end up here if ac3 selection didnt happen (no ac3 avail.) or if preferac3 is disabled. { if (IsPreferredAudioLanguageAvailable()) { //did we find a mpeg track that matches our LANG prefs ? idx = GetMpegAudioStreamIndexBasedOnLanguage(idxStreamIndexmpeg, mpegBasedOnLang, idxStreamIndexAc3, idx, idxFirstmpeg); } else { idx = idxFirstmpeg; Log.Info("Audio stream: switching to preferred MPEG audio stream {0}, NOT based on LANG", idx); } } if (idx == -1) { idx = 0; Log.Info("Audio stream: no preferred stream found - switching to audio stream 0"); } return idx; } private static int GetAC3AudioStreamIndex(int idxStreamIndexmpeg, int idxStreamIndexAc3, string ac3BasedOnLang, int idx, int idxFirstAc3) { if (_preferAC3) { if (IsPreferredAudioLanguageAvailable()) { //did we find an ac3 track that matches our LANG prefs ? idx = GetAC3AudioStreamIndexBasedOnLanguage(idxStreamIndexmpeg, idxStreamIndexAc3, ac3BasedOnLang, idx, idxFirstAc3); //if not then proceed with mpeg lang. selection below. } else { //did we find an ac3 track ? if (idxFirstAc3 > -1) { idx = idxFirstAc3; Log.Info("Audio stream: switching to preferred AC3 audio stream {0}, NOT based on LANG", idx); } //if not then proceed with mpeg lang. selection below. } } return idx; } private static void UpdateAudioStreamIndexesAndPrioritiesBasedOnLanguage(IAudioStream[] streams, int priority, ref int idxStreamIndexmpeg, ref string mpegBasedOnLang, ref int idxStreamIndexAc3, int idxLangPriAc3, int idxLangPrimpeg, ref string ac3BasedOnLang, out eAudioDualMonoMode dualMonoMode) { dualMonoMode = eAudioDualMonoMode.UNSUPPORTED; if (IsPreferredAudioLanguageAvailable()) { for (int i = 0; i < streams.Length; i++) { //now find the ones based on LANG prefs. if (ShouldApplyDualMonoMode(streams[i].Language)) { dualMonoMode = GetDualMonoMode(streams, i, ref priority, ref idxStreamIndexmpeg, ref mpegBasedOnLang); if (dualMonoMode != eAudioDualMonoMode.UNSUPPORTED) { break; } } else { // lower value means higher priority UpdateAudioStreamIndexesBasedOnLang(streams, i, ref idxStreamIndexmpeg, ref idxStreamIndexAc3, ref mpegBasedOnLang, ref idxLangPriAc3, ref idxLangPrimpeg, ref ac3BasedOnLang); } } } } private static int GetMpegAudioStreamIndexBasedOnLanguage(int idxStreamIndexmpeg, string mpegBasedOnLang, int idxStreamIndexAc3, int idx, int idxFirstmpeg) { if (idxStreamIndexmpeg > -1) { idx = idxStreamIndexmpeg; Log.Info("Audio stream: switching to preferred MPEG audio stream {0}, based on LANG {1}", idx, mpegBasedOnLang); } //if not, did we even find a mpeg track ? else if (idxFirstmpeg > -1) { //we did find a AC3 track, but not based on LANG - should we choose this or the mpeg track which is based on LANG. if (_preferAudioTypeOverLang || (idxStreamIndexAc3 == -1 && _preferAudioTypeOverLang)) { idx = idxFirstmpeg; Log.Info( "Audio stream: switching to preferred MPEG audio stream {0}, NOT based on LANG (none avail. matching {1})", idx, mpegBasedOnLang); } else if (idxStreamIndexAc3 > -1) { idx = idxStreamIndexAc3; Log.Info("Audio stream: ignoring MPEG audio stream {0}", idx); } } return idx; } private static int GetAC3AudioStreamIndexBasedOnLanguage(int idxStreamIndexmpeg, int idxStreamIndexAc3, string ac3BasedOnLang, int idx, int idxFirstAc3) { if (idxStreamIndexAc3 > -1) { idx = idxStreamIndexAc3; Log.Info("Audio stream: switching to preferred AC3 audio stream {0}, based on LANG {1}", idx, ac3BasedOnLang); } //if not, did we even find an ac3 track ? else if (idxFirstAc3 > -1) { //we did find an AC3 track, but not based on LANG - should we choose this or the mpeg track which is based on LANG. if (_preferAudioTypeOverLang || idxStreamIndexmpeg == -1) { idx = idxFirstAc3; Log.Info( "Audio stream: switching to preferred AC3 audio stream {0}, NOT based on LANG (none avail. matching {1})", idx, ac3BasedOnLang); } else { Log.Info("Audio stream: ignoring AC3 audio stream {0}", idxFirstAc3); } } return idx; } private static void UpdateAudioStreamIndexesBasedOnLang(IAudioStream[] streams, int i, ref int idxStreamIndexmpeg, ref int idxStreamIndexAc3, ref string mpegBasedOnLang, ref int idxLangPriAc3, ref int idxLangPrimpeg, ref string ac3BasedOnLang) { int langPriority = _preferredLanguages.IndexOf(streams[i].Language); string langSel = streams[i].Language; Log.Debug("Stream {0} lang {1}, lang priority index {2}", i, langSel, langPriority); // is the stream language preferred? if (langPriority >= 0) { // has the stream a higher priority than an old one or is this the first AC3 stream with lang pri (idxLangPriAc3 == -1) (AC3) bool isAC3 = IsStreamAC3(streams[i]); if (isAC3) { if (idxLangPriAc3 == -1 || langPriority < idxLangPriAc3) { Log.Debug("Setting AC3 pref"); idxStreamIndexAc3 = i; idxLangPriAc3 = langPriority; ac3BasedOnLang = langSel; } } else //not AC3 { // has the stream a higher priority than an old one or is this the first mpeg stream with lang pri (idxLangPrimpeg == -1) (mpeg) if (idxLangPrimpeg == -1 || langPriority < idxLangPrimpeg) { Log.Debug("Setting mpeg pref"); idxStreamIndexmpeg = i; idxLangPrimpeg = langPriority; mpegBasedOnLang = langSel; } } } } private static bool IsStreamAC3(IAudioStream stream) { return (stream.StreamType == AudioStreamType.AC3 || stream.StreamType == AudioStreamType.EAC3); } private static bool ShouldApplyDualMonoMode(string language) { bool dualMonoModeEnabled = (g_Player.GetAudioDualMonoMode() != eAudioDualMonoMode.UNSUPPORTED); return (dualMonoModeEnabled && language.Length == 6); } private static int GetFirstAC3Index(IAudioStream[] streams) { int idxFirstAc3 = -1; for (int i = 0; i < streams.Length; i++) { if (IsStreamAC3(streams[i])) { idxFirstAc3 = i; break; } } return idxFirstAc3; } private static int GetFirstMpegIndex(IAudioStream[] streams) { int idxFirstMpeg = -1; for (int i = 0; i < streams.Length; i++) { if (!IsStreamAC3(streams[i])) { idxFirstMpeg = i; break; } } return idxFirstMpeg; } private static eAudioDualMonoMode GetDualMonoMode(IAudioStream[] streams, int currentIndex, ref int priority, ref int idxStreamIndexmpeg, ref string mpegBasedOnLang) { eAudioDualMonoMode dualMonoMode = eAudioDualMonoMode.UNSUPPORTED; string leftAudioLang = streams[currentIndex].Language.Substring(0, 3); string rightAudioLang = streams[currentIndex].Language.Substring(3, 3); int indexLeft = _preferredLanguages.IndexOf(leftAudioLang); if (indexLeft >= 0 && indexLeft < priority) { dualMonoMode = eAudioDualMonoMode.LEFT_MONO; mpegBasedOnLang = leftAudioLang; idxStreamIndexmpeg = currentIndex; priority = indexLeft; } int indexRight = _preferredLanguages.IndexOf(rightAudioLang); if (indexRight >= 0 && indexRight < priority) { dualMonoMode = eAudioDualMonoMode.RIGHT_MONO; mpegBasedOnLang = rightAudioLang; idxStreamIndexmpeg = currentIndex; priority = indexRight; } return dualMonoMode; } private static bool IsPreferredAudioLanguageAvailable() { return (_preferredLanguages != null && _preferredLanguages.Count > 0); } private static IAudioStream[] GetStreamsList() { List streamsList = new List(); for (int i = 0; i < g_Player.AudioStreams; i++) { DVBAudioStream stream = new DVBAudioStream(); string streamType = g_Player.AudioType(i); switch (streamType) { case "AC3": stream.StreamType = AudioStreamType.AC3; break; case "AC3plus": stream.StreamType = AudioStreamType.EAC3; break; case "Mpeg1": stream.StreamType = AudioStreamType.Mpeg1; break; case "Mpeg2": stream.StreamType = AudioStreamType.Mpeg2; break; case "AAC": stream.StreamType = AudioStreamType.AAC; break; case "LATMAAC": stream.StreamType = AudioStreamType.LATMAAC; break; default: stream.StreamType = AudioStreamType.Unknown; break; } stream.Language = g_Player.AudioLanguage(i); string[] lang = stream.Language.Split('('); if (lang.Length > 1) { stream.Language = lang[1].Substring(0, lang[1].Length - 1); } streamsList.Add(stream); } return streamsList.ToArray(); } #endregion private static void ChannelTuneFailedNotifyUser(TvResult succeeded, bool wasPlaying, Channel channel) { GUIGraphicsContext.RenderBlackImage = false; _lastError.Result = succeeded; _lastError.FailingChannel = channel; _lastError.Messages.Clear(); int TextID = 0; _lastError.Messages.Add(GUILocalizeStrings.Get(1500)); switch (succeeded) { case TvResult.NoPmtFound: TextID = 1498; break; case TvResult.NoSignalDetected: TextID = 1499; break; case TvResult.CardIsDisabled: TextID = 1501; break; case TvResult.AllCardsBusy: TextID = 1502; break; case TvResult.ChannelIsScrambled: TextID = 1503; break; case TvResult.NoVideoAudioDetected: TextID = 1504; break; case TvResult.UnableToStartGraph: TextID = 1505; break; case TvResult.TuneCancelled: TextID = 1524; break; case TvResult.UnknownError: // this error can also happen if we have no connection to the server. if (!Connected) // || !IsRemotingConnected()) { TextID = 1510; } else { TextID = 1506; } break; case TvResult.UnknownChannel: TextID = 1507; break; case TvResult.ChannelNotMappedToAnyCard: TextID = 1508; break; case TvResult.NoTuningDetails: TextID = 1509; break; case TvResult.GraphBuildingFailed: TextID = 1518; break; case TvResult.SWEncoderMissing: TextID = 1519; break; case TvResult.NoFreeDiskSpace: TextID = 1520; break; default: // this error can also happen if we have no connection to the server. if (!Connected) // || !IsRemotingConnected()) { TextID = 1510; } else { TextID = 1506; } break; } if (TextID != 0) { _lastError.Messages.Add(GUILocalizeStrings.Get(TextID)); } GUIDialogNotify pDlgNotify = (GUIDialogNotify)GUIWindowManager.GetWindow((int)Window.WINDOW_DIALOG_NOTIFY); string caption = GUILocalizeStrings.Get(605) + " - " + _lastError.FailingChannel.DisplayName; pDlgNotify.SetHeading(caption); //my tv pDlgNotify.SetImage(TVUtil.GetChannelLogo(_lastError.FailingChannel)); StringBuilder sbMessage = new StringBuilder(); // ignore the "unable to start timeshift" line to avoid scrolling, because NotifyDLG has very few space available. for (int idx = 1; idx < _lastError.Messages.Count; idx++) { sbMessage.AppendFormat("\n{0}", _lastError.Messages[idx]); } pDlgNotify.SetText(sbMessage.ToString()); // Fullscreen shows the TVZapOSD to handle error messages if (GUIWindowManager.ActiveWindow == (int)(int)Window.WINDOW_TVFULLSCREEN) { // If failed and wasPlaying TV, left screen as it is and show osd with error message Log.Info("send message to fullscreen tv"); GUIMessage msg = new GUIMessage(GUIMessage.MessageType.GUI_MSG_TV_ERROR_NOTIFY, GUIWindowManager.ActiveWindow, 0, 0, 0, 0, null); msg.SendToTargetWindow = true; msg.TargetWindowId = (int)(int)Window.WINDOW_TVFULLSCREEN; msg.Object = _lastError; // forward error info object msg.Param1 = 3; // sec timeout GUIGraphicsContext.SendMessage(msg); return; } else { // show notify dialog pDlgNotify.DoModal((int)GUIWindowManager.ActiveWindowEx); } } private static void OnBlackImageRendered() { if (GUIGraphicsContext.RenderBlackImage) { //MediaPortal.GUI.Library.Log.Debug("TvHome.OnBlackImageRendered()"); _waitForBlackScreen.Set(); } } private static void OnVideoReceived() { if (GUIGraphicsContext.RenderBlackImage) { Log.Debug("TvHome.OnVideoReceived() {0}", FramesBeforeStopRenderBlackImage); if (FramesBeforeStopRenderBlackImage != 0) { FramesBeforeStopRenderBlackImage--; if (FramesBeforeStopRenderBlackImage == 0) { GUIGraphicsContext.RenderBlackImage = false; Log.Debug("TvHome.StopRenderBlackImage()"); } } } } private static void StopRenderBlackImage() { if (GUIGraphicsContext.RenderBlackImage) { FramesBeforeStopRenderBlackImage = 3; // Ambass : we need to wait the 3rd frame to avoid persistance of previous channel....Why ????? // Morpheus: number of frames depends on hardware, from 1..5 or higher might be needed! // Probably the faster the graphics card is, the more frames required??? } } private static void RenderBlackImage() { if (GUIGraphicsContext.RenderBlackImage == false) { Log.Debug("TvHome.RenderBlackImage()"); _waitForBlackScreen.Reset(); GUIGraphicsContext.RenderBlackImage = true; _waitForBlackScreen.WaitOne(1000, false); } } /// /// Pre-tune checks "outsourced" to reduce code complexity /// /// the channel to tune /// indicate to continue /// return value when not continuing private static bool PreTuneChecks(Channel channel, out bool doContinue) { doContinue = false; if (_suspended && _waitonresume > 0) { Log.Info("TVHome.ViewChannelAndCheck(): system just woke up...waiting {0} ms., suspended {2}", _waitonresume, _suspended); Thread.Sleep(_waitonresume); } _waitForVideoReceived.Reset(); if (channel == null) { Log.Info("TVHome.ViewChannelAndCheck(): channel==null"); return false; } Log.Info("TVHome.ViewChannelAndCheck(): View channel={0}", channel.DisplayName); //if a channel is untunable, then there is no reason to carry on or even stop playback. var userCopy = Card.User.Clone() as IUser; if (userCopy != null) { userCopy.Name = Dns.GetHostName(); ChannelState CurrentChanState = TvServer.GetChannelState(channel.IdChannel, userCopy); if (CurrentChanState == ChannelState.nottunable) { ChannelTuneFailedNotifyUser(TvResult.AllCardsBusy, false, channel); return false; } } //BAV: fixing mantis bug 1263: TV starts with no video if Radio is previously ON & channel selected from TV guide if ((!channel.IsRadio && g_Player.IsRadio) || (channel.IsRadio && !g_Player.IsRadio)) { Log.Info("TVHome.ViewChannelAndCheck(): Stop g_Player"); g_Player.Stop(true); } // do we stop the player when changing channel ? // _userChannelChanged is true if user did interactively change the channel, like with mini ch. list. etc. if (!_userChannelChanged) { if (g_Player.IsTVRecording) { return true; } if (!_autoTurnOnTv) //respect the autoturnontv setting. { if (g_Player.IsVideo || g_Player.IsDVD || g_Player.IsMusic) { return true; } } else { if (g_Player.IsVideo || g_Player.IsDVD || g_Player.IsMusic || g_Player.IsCDA) // || g_Player.IsRadio) { g_Player.Stop(true); // tell that we are zapping so exclusive mode is not going to be disabled } } } else if (g_Player.IsTVRecording && _userChannelChanged) //we are watching a recording, we have now issued a ch. change..stop the player. { _userChannelChanged = false; g_Player.Stop(true); } else if ((channel.IsTv && g_Player.IsRadio) || (channel.IsRadio && g_Player.IsTV) || g_Player.IsCDA || g_Player.IsMusic || g_Player.IsVideo) { g_Player.Stop(true); } if (Card != null) { if (g_Player.Playing && g_Player.IsTV && !g_Player.IsTVRecording) //modified by joboehl. Avoids other video being played instead of TV. { //if we're already watching this channel, then simply return if (Card.IsTimeShifting == true && Card.IdChannel == channel.IdChannel) { return true; } } } // if all checks passed then we won't return doContinue = true; return true; // will be ignored } /// /// Tunes to a new channel /// /// /// public static bool ViewChannelAndCheck(Channel channel) { bool checkResult; bool doContinue; if (!Connected) { return false; } _status.Clear(); _doingChannelChange = false; try { checkResult = PreTuneChecks(channel, out doContinue); if (doContinue == false) return checkResult; _doingChannelChange = true; TvResult succeeded; IUser user = new User(); if (Card != null) { user.CardId = Card.Id; } if ((g_Player.Playing && g_Player.IsTimeShifting && !g_Player.Stopped) && (g_Player.IsTV || g_Player.IsRadio)) { _status.Set(LiveTvStatus.WasPlaying); } //Start timeshifting the new tv channel TvServer server = new TvServer(); VirtualCard card; int newCardId = -1; // check which card will be used newCardId = server.TimeShiftingWouldUseCard(ref user, channel.IdChannel); //Added by joboehl - If any major related to the timeshifting changed during the start, restart the player. if (newCardId != -1 && Card.Id != newCardId) { _status.Set(LiveTvStatus.CardChange); RegisterCiMenu(newCardId); } // we need to stop player HERE if card has changed. if (_status.AllSet(LiveTvStatus.WasPlaying | LiveTvStatus.CardChange)) { Log.Debug("TVHome.ViewChannelAndCheck(): Stopping player. CardId:{0}/{1}, RTSP:{2}", Card.Id, newCardId, Card.RTSPUrl); Log.Debug("TVHome.ViewChannelAndCheck(): Stopping player. Timeshifting:{0}", Card.TimeShiftFileName); Log.Debug("TVHome.ViewChannelAndCheck(): rebuilding graph (card changed) - timeshifting continueing."); } if (_status.IsSet(LiveTvStatus.WasPlaying)) { RenderBlackImage(); g_Player.PauseGraph(); } else { // if CI menu is not attached due to card change, do it if graph was not playing // (some handlers use polling threads that get stopped on graph stop) if (_status.IsNotSet(LiveTvStatus.CardChange)) RegisterCiMenu(newCardId); } // if card was not changed if (_status.IsNotSet(LiveTvStatus.CardChange)) { g_Player.OnZapping(0x80); // Setup Zapping for TsReader, requesting new PAT from stream } bool cardChanged = false; succeeded = server.StartTimeShifting(ref user, channel.IdChannel, out card, out cardChanged); if (_status.IsSet(LiveTvStatus.WasPlaying)) { if (card != null) g_Player.OnZapping((int)card.Type); else g_Player.OnZapping(-1); } if (succeeded != TvResult.Succeeded) { //timeshifting new channel failed. g_Player.Stop(); // ensure right channel name, even if not watchable:Navigator.Channel = channel; ChannelTuneFailedNotifyUser(succeeded, _status.IsSet(LiveTvStatus.WasPlaying), channel); _doingChannelChange = true; // keep fullscreen false; return true; // "success" } if (card != null && card.NrOfOtherUsersTimeshiftingOnCard > 0) { _status.Set(LiveTvStatus.SeekToEndAfterPlayback); } if (cardChanged) { _status.Set(LiveTvStatus.CardChange); if (card != null) { RegisterCiMenu(card.Id); } _status.Reset(LiveTvStatus.WasPlaying); } else { _status.Reset(LiveTvStatus.CardChange); _status.Set(LiveTvStatus.SeekToEnd); } // Update channel navigator if (Navigator.Channel != null && (channel.IdChannel != Navigator.Channel.IdChannel || (Navigator.LastViewedChannel == null))) { Navigator.LastViewedChannel = Navigator.Channel; } Log.Info("succeeded:{0} {1}", succeeded, card); Card = card; //Moved by joboehl - Only touch the card if starttimeshifting succeeded. // if needed seek to end if (_status.IsSet(LiveTvStatus.SeekToEnd)) { SeekToEnd(true); } // continue graph g_Player.ContinueGraph(); if (!g_Player.Playing || _status.IsSet(LiveTvStatus.CardChange) || (g_Player.Playing && !(g_Player.IsTV || g_Player.IsRadio))) { StartPlay(); // if needed seek to end if (_status.IsSet(LiveTvStatus.SeekToEndAfterPlayback)) { double dTime = g_Player.Duration - 5; g_Player.SeekAbsolute(dTime); } } try { TvTimeShiftPositionWatcher.SetNewChannel(channel.IdChannel); } catch { //ignore, error already logged } _playbackStopped = false; _doingChannelChange = false; _ServerNotConnectedHandled = false; return true; } catch (Exception ex) { Log.Debug("TvPlugin:ViewChannelandCheckV2 Exception {0}", ex.ToString()); _doingChannelChange = false; Card.User.Name = new User().Name; g_Player.Stop(); Card.StopTimeShifting(); return false; } finally { StopRenderBlackImage(); _userChannelChanged = false; FireOnChannelChangedEvent(); Navigator.UpdateCurrentChannel(); } } private static void FireOnChannelChangedEvent() { if (OnChannelChanged != null) { OnChannelChanged(); } } public static void ViewChannel(Channel channel) { ViewChannelAndCheck(channel); UpdateProgressPercentageBar(); return; } /// /// When called this method will switch to the next TV channel /// public static void OnNextChannel() { Log.Info("TVHome:OnNextChannel()"); if (GUIGraphicsContext.IsFullScreenVideo) { // where in fullscreen so delayzap channel instead of immediatly tune.. TvFullScreen TVWindow = (TvFullScreen)GUIWindowManager.GetWindow((int)(int)Window.WINDOW_TVFULLSCREEN); if (TVWindow != null) { TVWindow.ZapNextChannel(); } return; } // Zap to next channel immediately Navigator.ZapToNextChannel(false); } /// /// When called this method will switch to the last viewed TV channel // mPod /// public static void OnLastViewedChannel() { Navigator.ZapToLastViewedChannel(); } /// /// Returns true if the specified window belongs to the my tv plugin /// /// id of window /// /// true: belongs to the my tv plugin /// false: does not belong to the my tv plugin public static bool IsTVWindow(int windowId) { if (windowId == (int)Window.WINDOW_TV) { return true; } return false; } /// /// Gets the channel navigator that can be used for channel zapping. /// public static ChannelNavigator Navigator { get { return m_navigator; } } private static void StartPlay() { Stopwatch benchClock = null; benchClock = Stopwatch.StartNew(); if (Card == null) { Log.Info("tvhome:startplay card=null"); return; } if (Card.IsScrambled) { Log.Info("tvhome:startplay scrambled"); return; } Log.Info("tvhome:startplay"); string timeshiftFileName = Card.TimeShiftFileName; Log.Info("tvhome:file:{0}", timeshiftFileName); TvLibrary.Interfaces.IChannel channel = Card.Channel; if (channel == null) { Log.Info("tvhome:startplay channel=null"); return; } g_Player.MediaType mediaType = g_Player.MediaType.TV; if (channel.IsRadio) { mediaType = g_Player.MediaType.Radio; } benchClock.Stop(); Log.Warn("tvhome:startplay. Phase 1 - {0} ms - Done method initialization", benchClock.ElapsedMilliseconds.ToString()); benchClock.Reset(); benchClock.Start(); timeshiftFileName = TVUtil.GetFileNameForTimeshifting(); bool useRTSP = UseRTSP(); Log.Info("tvhome:startplay:{0} - using rtsp mode:{1}", timeshiftFileName, useRTSP); if (!useRTSP) { bool tsFileExists = false; int timeout = 0; while (!tsFileExists && timeout < 50) { tsFileExists = File.Exists(timeshiftFileName); if (!tsFileExists) { Log.Info("tvhome:startplay: waiting for TS file {0}", timeshiftFileName); timeout++; Thread.Sleep(10); } } } if (!g_Player.Play(timeshiftFileName, mediaType)) { StopPlayback(); } benchClock.Stop(); Log.Warn("tvhome:startplay. Phase 2 - {0} ms - Done starting g_Player.Play()", benchClock.ElapsedMilliseconds.ToString()); benchClock.Reset(); //benchClock.Start(); //SeekToEnd(true); //Log.Warn("tvhome:startplay. Phase 3 - {0} ms - Done seeking.", benchClock.ElapsedMilliseconds.ToString()); //SeekToEnd(true); benchClock.Stop(); } private static void SeekToEnd(bool zapping) { double duration = g_Player.Duration; double position = g_Player.CurrentPosition; bool useRtsp = UseRTSP(); Log.Info("tvhome:SeektoEnd({0}/{1}),{2},rtsp={3}", position, duration, zapping, useRtsp); if (duration > 0 || position > 0) { try { //singleseat or multiseat rtsp streaming.... if (!useRtsp || (useRtsp && zapping)) { g_Player.SeekAbsolute(duration); } } catch (Exception e) { Log.Error("tvhome:SeektoEnd({0}, rtsp={1} exception: {2}", zapping, useRtsp, e.Message); g_Player.Stop(); } } } #region CI Menu /// /// Register the remoting service and attaching ciMenuHandler for server events /// public static void RegisterCiMenu(int newCardId) { if (ciMenuHandler == null) { Log.Debug("CiMenu: PrepareCiMenu"); ciMenuHandler = new CiMenuHandler(); } // Check if card supports CI menu if (newCardId != -1 && RemoteControl.Instance.CiMenuSupported(newCardId)) { // opens remoting and attach local eventhandler to server event, call only once RemoteControl.RegisterCiMenuCallbacks(ciMenuHandler); // Enable CI menu handling in card RemoteControl.Instance.SetCiMenuHandler(newCardId, null); Log.Debug("TvPlugin: CiMenuHandler attached to new card {0}", newCardId); } } /// /// Keyboard input for ci menu /// /// /// /// /// /// protected static bool GetKeyboard(string title, int maxLength, bool bPassword, ref string strLine) { StandardKeyboard keyboard = (StandardKeyboard)GUIWindowManager.GetWindow((int)Window.WINDOW_VIRTUAL_KEYBOARD); if (null == keyboard) { return false; } keyboard.Password = bPassword; //keyboard.Title = title; keyboard.SetMaxLength(maxLength); keyboard.Reset(); keyboard.Text = strLine; keyboard.DoModal(GUIWindowManager.ActiveWindowEx); if (keyboard.IsConfirmed) { strLine = keyboard.Text; return true; } return false; } /// /// Pass the CiMenu to TvHome so that Process can handle it in own thread /// /// public static void ProcessCiMenu(CiMenu Menu) { lock (CiMenuLock) { CiMenuList.Add(Menu); if (CiMenuActive) // Just suppose if a new menu is coming from CAM, last one can be trashed. dlgCiMenu.Reset(); Log.Debug("ProcessCiMenu {0} {1} ", Menu, CiMenuList.Count); } } /// /// Handles all CiMenu actions from callback /// public static void ShowCiMenu() { lock (CiMenuLock) { if (CiMenuActive || CiMenuList.Count == 0) return; currentCiMenu = CiMenuList[0]; CiMenuList.RemoveAt(0); CiMenuActive = true; // avoid re-entrance from process() } if (dlgCiMenu == null) { dlgCiMenu = (GUIDialogCIMenu) GUIWindowManager.GetWindow((int)MediaPortal.GUI.Library.GUIWindow.Window.WINDOW_DIALOG_CIMENU); } switch (currentCiMenu.State) { // choices available, so show them case TvLibrary.Interfaces.CiMenuState.Ready: dlgCiMenu.Reset(); dlgCiMenu.SetHeading(currentCiMenu.Title, currentCiMenu.Subtitle, currentCiMenu.BottomText); // CI Menu for (int i = 0; i < currentCiMenu.NumChoices; i++) // CI Menu Entries dlgCiMenu.Add(currentCiMenu.MenuEntries[i].Message); // take only message, numbers come from dialog // show dialog and wait for result dlgCiMenu.DoModal(GUIWindowManager.ActiveWindow); if (currentCiMenu.State != TvLibrary.Interfaces.CiMenuState.Error) { if (dlgCiMenu.SelectedId != -1) { TVHome.Card.SelectCiMenu(Convert.ToByte(dlgCiMenu.SelectedId)); } else { if (CiMenuList.Count == 0) // Another menu is pending, do not answer... TVHome.Card.SelectCiMenu(0); // 0 means "back" } } else { TVHome.Card.CloseMenu(); // in case of error close the menu } break; // errors and menu options with no choices case TvLibrary.Interfaces.CiMenuState.Error: case TvLibrary.Interfaces.CiMenuState.NoChoices: if (_dialogNotify == null) { _dialogNotify = (GUIDialogNotify)GUIWindowManager.GetWindow((int)Window.WINDOW_DIALOG_NOTIFY); } if (null != _dialogNotify) { _dialogNotify.Reset(); _dialogNotify.ClearAll(); _dialogNotify.SetHeading(currentCiMenu.Title); _dialogNotify.SetText(String.Format("{0}\r\n{1}", currentCiMenu.Subtitle, currentCiMenu.BottomText)); _dialogNotify.TimeOut = 2; // seconds _dialogNotify.DoModal(GUIWindowManager.ActiveWindow); } break; // requests require users input so open keyboard case TvLibrary.Interfaces.CiMenuState.Request: String result = ""; if ( GetKeyboard(currentCiMenu.RequestText, currentCiMenu.AnswerLength, currentCiMenu.Password, ref result) == true) { TVHome.Card.SendMenuAnswer(false, result); // send answer, cancel=false } else { TVHome.Card.SendMenuAnswer(true, null); // cancel request } break; case CiMenuState.Close: if (_dialogNotify != null) { GUIMessage msg = new GUIMessage(GUIMessage.MessageType.GUI_MSG_WINDOW_DEINIT, _dialogNotify.GetID, 0, 0, 0, 0, null); _dialogNotify.OnMessage(msg); // Send a de-init msg to hide the current notify dialog } if (dlgCiMenu != null) { GUIMessage msg = new GUIMessage(GUIMessage.MessageType.GUI_MSG_WINDOW_DEINIT, dlgCiMenu.GetID, 0, 0, 0, 0, null); dlgCiMenu.OnMessage(msg); // Send a de-init msg to hide the current CI menu dialog } break; } CiMenuActive = false; // finished currentCiMenu = null; // reset menu } #endregion } } #region CI Menu /// /// Handler class for gui interactions of ci menu /// public class CiMenuHandler : CiMenuCallbackSink { /// /// eventhandler to show CI Menu dialog /// /// protected override void CiMenuCallback(CiMenu Menu) { try { Log.Debug("Callback from tvserver {0}", Menu.Title); // pass menu to calling dialog TvPlugin.TVHome.ProcessCiMenu(Menu); } catch { Menu = new CiMenu("Remoting Exception", "Communication with server failed", null, TvLibrary.Interfaces.CiMenuState.Error); // pass menu to calling dialog TvPlugin.TVHome.ProcessCiMenu(Menu); } } } #endregion