From 3bca712cd12330d7b88abfb35cfdc86f94f5ad11 Mon Sep 17 00:00:00 2001
From: Smeulf <smeulf@team-mediaportal.com>
Date: Sun, 18 Dec 2011 17:05:43 +0100
Subject: [PATCH] VideoPlayer is now a IChapter player : Chapters are
 supported for any king of video (IE MKV). Thanks to
 Morpheus for the help.

Moved DVD Titles Methods to ITitlePlayer : Future use for MKV edition support
---
 MediaPortal/Incubator/BDHandler/BDPlayer.cs        |   99 +-----------
 .../Incubator/SlimTvClient/Player/LiveTvPlayer.cs  |  141 +++++++++--------
 .../Core/MediaPortal.UI/MediaPortal.UI.csproj      |    1 +
 .../Presentation/Players/IDVDPlayer.cs             |   19 +--
 .../Presentation/Players/ITitlePlayer.cs           |   27 +++
 .../Source/UI/Players/VideoPlayer/DvdPlayer.cs     |   63 ++------
 .../Source/UI/Players/VideoPlayer/VideoPlayer.cs   |  170 +++++++++++++++++++-
 .../Models/DefaultVideoPlayerUIContributor.cs      |  110 ++++++++++++-
 .../default/themes/default/styles/MediaStyles.xaml |   11 ++-
 9 files changed, 400 insertions(+), 241 deletions(-)
 create mode 100644 MediaPortal/Source/Core/MediaPortal.UI/Presentation/Players/ITitlePlayer.cs

diff --git a/MediaPortal/Incubator/BDHandler/BDPlayer.cs b/MediaPortal/Incubator/BDHandler/BDPlayer.cs
index 08cd568..2605ad9 100644
--- a/MediaPortal/Incubator/BDHandler/BDPlayer.cs
+++ b/MediaPortal/Incubator/BDHandler/BDPlayer.cs
@@ -46,8 +46,7 @@ namespace MediaPortal.UI.Players.Video
     #region Consts and delegates
        
     public const double MINIMAL_FULL_FEATURE_LENGTH = 3000;
-    public const string RES_PLAYBACK_CHAPTER = "[Playback.Chapter]";
-
+    
     /// <summary>
     /// Delegate for starting a BDInfo thread.
     /// </summary>
@@ -57,13 +56,6 @@ namespace MediaPortal.UI.Players.Video
 
     #endregion
 
-    #region Variables
-
-    private double[] _chapterTimestamps;
-    private string[] _chapterNames;
-
-    #endregion
-
     #region Constructor
 
     /// <summary>
@@ -255,103 +247,26 @@ namespace MediaPortal.UI.Players.Video
         _chapterNames[c] = GetChapterName(c + 1);
     }
 
-    #region IDVDPlayer Member
+    #region ITitlePlayer Member
 
     private readonly string[] _emptyStringArray = new string[0];
 
-    public string[] DvdTitles
+    public string[] Titles
     {
       get { return _emptyStringArray; }
     }
 
-    public void SetDvdTitle(string title)
+    public void SetTitle(string title)
     { }
 
-    public string CurrentDvdTitle
+    public string CurrentTitle
     {
       get { return null; }
     }
 
-    /// <summary>
-    /// Returns a localized chapter name.
-    /// </summary>
-    /// <param name="chapterNumber">0 based chapter number.</param>
-    /// <returns>Localized chapter name.</returns>
-    private static String GetChapterName(int chapterNumber)
-    {
-      //Idea: we could scrape chapter names and store them in MediaAspects. When they are available, return the full names here.
-      return ServiceRegistration.Get<ILocalization>().ToString(RES_PLAYBACK_CHAPTER, chapterNumber);
-    }
-
-    public string[] Chapters
-    {
-      get { return _chapterNames; }
-    }
-
-    public void SetChapter(string chapter)
-    {
-      string[] chapters = Chapters;
-      for (int i = 0; i < chapters.Length; i++)
-      {
-        if (chapter == chapters[i])
-        {
-          SetDvdChapter(i);
-          return;
-        }
-      }
-    }
-
-    private void SetDvdChapter(Int32 chapterIndex)
-    {
-      if (chapterIndex > _chapterTimestamps.Length || chapterIndex < 0)
-        return;
-      TimeSpan seekTo = TimeSpan.FromSeconds(_chapterTimestamps[chapterIndex]);
-      CurrentTime = seekTo;
-      return;
-    }
-
-    public bool ChaptersAvailable
-    {
-      get { return _chapterNames != null; }
-    }
-
-    private bool GetCurrentChapterIndex(out Int32 chapterIndex)
-    {
-      double currentTimestamp = CurrentTime.TotalSeconds;
-      for (int c = _chapterTimestamps.Length - 1; c >= 0; c--)
-      {
-        if (currentTimestamp > _chapterTimestamps[c])
-        {
-          chapterIndex = c;
-          return true;
-        }
-      }
-      chapterIndex = 0;
-      return false;
-    }
-
-    public void NextChapter()
-    {
-      Int32 currentChapter;
-      if (GetCurrentChapterIndex(out currentChapter))
-      {
-        SetDvdChapter(currentChapter + 1);
-      }
-    }
-
-    public void PrevChapter()
-    {
-      Int32 currentChapter;
-      if (GetCurrentChapterIndex(out currentChapter))
-      {
-        SetDvdChapter(currentChapter - 1);
-      }
-    }
+    #endregion
 
-    public string CurrentChapter
-    {
-      get { return null; }
-    }
+    #region IDVDPlayer Member
 
     public bool IsHandlingUserInput
     {
diff --git a/MediaPortal/Incubator/SlimTvClient/Player/LiveTvPlayer.cs b/MediaPortal/Incubator/SlimTvClient/Player/LiveTvPlayer.cs
index c15d4ce..a378297 100644
--- a/MediaPortal/Incubator/SlimTvClient/Player/LiveTvPlayer.cs
+++ b/MediaPortal/Incubator/SlimTvClient/Player/LiveTvPlayer.cs
@@ -38,6 +38,15 @@ namespace MediaPortal.Plugins.SlimTvClient
 {
   public class LiveTvPlayer : TsVideoPlayer, IUIContributorPlayer, IChapterPlayer//, IReusablePlayer
   {
+    #region Variables
+
+    protected IList<ITimeshiftContext> _timeshiftContexes;
+    protected StreamInfoHandler _chapterInfo = null;
+
+    #endregion
+
+    #region ctor
+
     /// <summary>
     /// Constructs a LiveTvPlayer player object.
     /// </summary>
@@ -46,6 +55,8 @@ namespace MediaPortal.Plugins.SlimTvClient
       PlayerTitle = "LiveTvPlayer"; // for logging
     }
 
+    #endregion
+
     #region IUIContributorPlayer Member
 
     public Type UIContributorType
@@ -55,68 +66,6 @@ namespace MediaPortal.Plugins.SlimTvClient
 
     #endregion
 
-    #region IChapterPlayer Member
-
-    protected IList<ITimeshiftContext> _timeshiftContexes;
-    protected StreamInfoHandler _chapterInfo = null;
-
-    private void EnumerateChapters()
-    {
-      _chapterInfo = new StreamInfoHandler();
-
-      IPlayerContextManager playerContextManager = ServiceRegistration.Get<IPlayerContextManager>();
-      for (int index = 0; index < playerContextManager.NumActivePlayerContexts; index++)
-      {
-        IPlayerContext playerContext = playerContextManager.GetPlayerContext(index);
-        if (playerContext == null || playerContext.CurrentPlayer != this)
-          continue;
-
-        LiveTvMediaItem liveTvMediaItem = playerContext.CurrentMediaItem as LiveTvMediaItem;
-        if (liveTvMediaItem == null)
-          continue;
-
-        _timeshiftContexes = liveTvMediaItem.TimeshiftContexes;
-        int i = 0;
-        foreach (ITimeshiftContext timeshiftContext in _timeshiftContexes)
-        {
-          string program = timeshiftContext.Program != null ? timeshiftContext.Program.Title : 
-            ServiceRegistration.Get<ILocalization>().ToString("[SlimTvClient.NoProgram]");
-
-          _chapterInfo.AddUnique(new StreamInfo(null, i++,
-                                                string.Format("{0}: {1}", timeshiftContext.Channel.Name,
-                                                              program), 0));
-        }
-      }
-    }
-
-    public string[] Chapters
-    {
-      get {
-        lock (SyncObj)
-        {
-          //if (_chapterInfo == null)
-            EnumerateChapters();
-
-          return _chapterInfo.Count == 0 ? EMPTY_STRING_ARRAY : _chapterInfo.GetStreamNames();
-        }
-      }
-    }
-
-    public void SetChapter(string chapter)
-    {
-      lock (SyncObj)
-      {
-        if (_chapterInfo != null)
-        {
-          StreamInfo chapterInfo = _chapterInfo.FindStream(chapter);
-          if (chapterInfo != null)
-          {
-            CurrentTime = GetStartDuration(chapterInfo.StreamIndex);
-          }
-        }
-      }
-    }
-
     public ITimeshiftContext CurrentTimeshiftContext
     {
       get 
@@ -172,7 +121,67 @@ namespace MediaPortal.Plugins.SlimTvClient
       return totalTime;
     }
 
-    public bool ChaptersAvailable
+    private void EnumerateChapters()
+    {
+      _chapterInfo = new StreamInfoHandler();
+
+      IPlayerContextManager playerContextManager = ServiceRegistration.Get<IPlayerContextManager>();
+      for (int index = 0; index < playerContextManager.NumActivePlayerContexts; index++)
+      {
+        IPlayerContext playerContext = playerContextManager.GetPlayerContext(index);
+        if (playerContext == null || playerContext.CurrentPlayer != this)
+          continue;
+
+        LiveTvMediaItem liveTvMediaItem = playerContext.CurrentMediaItem as LiveTvMediaItem;
+        if (liveTvMediaItem == null)
+          continue;
+
+        _timeshiftContexes = liveTvMediaItem.TimeshiftContexes;
+        int i = 0;
+        foreach (ITimeshiftContext timeshiftContext in _timeshiftContexes)
+        {
+          string program = timeshiftContext.Program != null ? timeshiftContext.Program.Title :
+            ServiceRegistration.Get<ILocalization>().ToString("[SlimTvClient.NoProgram]");
+
+          _chapterInfo.AddUnique(new StreamInfo(null, i++,
+                                                string.Format("{0}: {1}", timeshiftContext.Channel.Name,
+                                                              program), 0));
+        }
+      }
+    }
+
+    #region IChapterPlayer overrides
+
+    public override string[] Chapters
+    {
+      get
+      {
+        lock (SyncObj)
+        {
+          //if (_chapterInfo == null)
+          EnumerateChapters();
+
+          return _chapterInfo.Count == 0 ? EMPTY_STRING_ARRAY : _chapterInfo.GetStreamNames();
+        }
+      }
+    }
+
+    public override void SetChapter(string chapter)
+    {
+      lock (SyncObj)
+      {
+        if (_chapterInfo != null)
+        {
+          StreamInfo chapterInfo = _chapterInfo.FindStream(chapter);
+          if (chapterInfo != null)
+          {
+            CurrentTime = GetStartDuration(chapterInfo.StreamIndex);
+          }
+        }
+      }
+    }
+
+    public override bool ChaptersAvailable
     {
       get
       {
@@ -186,7 +195,7 @@ namespace MediaPortal.Plugins.SlimTvClient
       }
     }
 
-    public void NextChapter()
+    public override void NextChapter()
     {
       int index;
       if (GetContextIndex(CurrentTime, out index))
@@ -194,7 +203,7 @@ namespace MediaPortal.Plugins.SlimTvClient
           CurrentTime = GetContextStart(index + 1);
     }
 
-    public void PrevChapter()
+    public override void PrevChapter()
     {
       int index;
       if (GetContextIndex(CurrentTime, out index))
@@ -202,7 +211,7 @@ namespace MediaPortal.Plugins.SlimTvClient
           CurrentTime = GetContextStart(index - 1);
     }
 
-    public string CurrentChapter
+    public override string CurrentChapter
     {
       get
       {
diff --git a/MediaPortal/Source/Core/MediaPortal.UI/MediaPortal.UI.csproj b/MediaPortal/Source/Core/MediaPortal.UI/MediaPortal.UI.csproj
index f1891c9..64ce409 100644
--- a/MediaPortal/Source/Core/MediaPortal.UI/MediaPortal.UI.csproj
+++ b/MediaPortal/Source/Core/MediaPortal.UI/MediaPortal.UI.csproj
@@ -73,6 +73,7 @@
     <Compile Include="General\WindowsMessaging.cs" />
     <Compile Include="Presentation\Actions\KeyAction.cs" />
     <Compile Include="Presentation\Players\ISpectrumPlayer.cs" />
+    <Compile Include="Presentation\Players\ITitlePlayer.cs" />
     <Compile Include="Presentation\SkinResources\ISkin.cs" />
     <Compile Include="Presentation\SkinResources\ISkinResourceBundle.cs" />
     <Compile Include="Presentation\SkinResources\ITheme.cs" />
diff --git a/MediaPortal/Source/Core/MediaPortal.UI/Presentation/Players/IDVDPlayer.cs b/MediaPortal/Source/Core/MediaPortal.UI/Presentation/Players/IDVDPlayer.cs
index dbbf966..1b12302 100644
--- a/MediaPortal/Source/Core/MediaPortal.UI/Presentation/Players/IDVDPlayer.cs
+++ b/MediaPortal/Source/Core/MediaPortal.UI/Presentation/Players/IDVDPlayer.cs
@@ -29,26 +29,9 @@ namespace MediaPortal.UI.Presentation.Players
   /// <summary>
   /// Interface of a DVD player.
   /// </summary>
-  public interface IDVDPlayer : IPlayer, IChapterPlayer
+  public interface IDVDPlayer : IPlayer, ITitlePlayer, IChapterPlayer
   {
     /// <summary>
-    /// Gets an ordered list of localized DVD titles.
-    /// </summary>
-    string[] DvdTitles { get; }
-
-    /// <summary>
-    /// Plays the given DVD title.
-    /// </summary>
-    /// <param name="title">The name of the title to set. Must be one of the title names from the
-    /// <see cref="DvdTitles"/> list.</param>
-    void SetDvdTitle(string title);
-
-    /// <summary>
-    /// Gets the current DVD title.
-    /// </summary>
-    string CurrentDvdTitle { get; }
-   
-    /// <summary>
     /// Gets the information whether the DVD player currently handles key and mouse events. This is the case if we
     /// are currently in DVD menu.
     /// </summary>
diff --git a/MediaPortal/Source/Core/MediaPortal.UI/Presentation/Players/ITitlePlayer.cs b/MediaPortal/Source/Core/MediaPortal.UI/Presentation/Players/ITitlePlayer.cs
new file mode 100644
index 0000000..b483cf2
--- /dev/null
+++ b/MediaPortal/Source/Core/MediaPortal.UI/Presentation/Players/ITitlePlayer.cs
@@ -0,0 +1,27 @@
+﻿using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MediaPortal.UI.Presentation.Players
+{
+  public interface ITitlePlayer
+  {
+    /// <summary>
+    /// Gets an ordered list of localized titles/editions.
+    /// </summary>
+    string[] Titles { get; }
+
+    /// <summary>
+    /// Plays the given title/edition.
+    /// </summary>
+    /// <param name="title">The name of the title/edition to set. Must be one of the title/edition names from the
+    /// <see cref="DvdTitles"/> list.</param>
+    void SetTitle(string title);
+
+    /// <summary>
+    /// Gets the current title/edition.
+    /// </summary>
+    string CurrentTitle { get; }
+  }
+}
diff --git a/MediaPortal/Source/UI/Players/VideoPlayer/DvdPlayer.cs b/MediaPortal/Source/UI/Players/VideoPlayer/DvdPlayer.cs
index 3749bda..622d7ff 100644
--- a/MediaPortal/Source/UI/Players/VideoPlayer/DvdPlayer.cs
+++ b/MediaPortal/Source/UI/Players/VideoPlayer/DvdPlayer.cs
@@ -59,8 +59,6 @@ namespace MediaPortal.UI.Players.Video
     protected const int WM_LBUTTONUP = 0x0202;
 
     public const string RES_PLAYBACK_TITLE = "[Playback.Title]";
-    public const string RES_PLAYBACK_CHAPTER = "[Playback.Chapter]";
-
     #endregion
 
     #region Variables
@@ -80,11 +78,6 @@ namespace MediaPortal.UI.Players.Video
     /// <summary> asynchronous command pending. </summary>
     protected bool _pendingCmd;
 
-    /// <summary>
-    /// List of chapters. Will be initialized lazily. <c>null</c> if not currently valid.
-    /// </summary>
-    protected string[] _chapters = null;
-
     protected DvdHMSFTimeCode _currTime; // copy of current playback states, see OnDvdEvent()
     protected string[] _titles;
     protected int _currTitle = 0;
@@ -641,7 +634,7 @@ namespace MediaPortal.UI.Players.Video
       {
         _streamInfoSubtitles = null; // Invalidate subtitles - could be different in other title
         _streamInfoAudio = null; // Invalidate audio streams - could be different in other title
-        _chapters = null; // Invalidate chapters - they are different per title
+        _chapterNames = null; // Invalidate chapters - they are different per title
         _currTitle = title;
       }
       EnumerateChapters();
@@ -894,12 +887,12 @@ namespace MediaPortal.UI.Players.Video
 
     #endregion
 
-    #region DVD Titles handling
+    #region DVD Titles handling (ITitlesPlayer members)
 
     /// <summary>
     /// Gets the available DVD titles.
     /// </summary>
-    public string[] DvdTitles
+    public string[] Titles
     {
       get
       {
@@ -921,9 +914,9 @@ namespace MediaPortal.UI.Players.Video
     /// Sets the DVD title to play.
     /// </summary>
     /// <param name="title">DVD title</param>
-    public void SetDvdTitle(string title)
+    public void SetTitle(string title)
     {
-      string[] titles = DvdTitles;
+      string[] titles = Titles;
       for (int i = 0; i < titles.Length; ++i)
       {
         if (title == titles[i])
@@ -937,7 +930,7 @@ namespace MediaPortal.UI.Players.Video
     /// <summary>
     /// Gets the current DVD title.
     /// </summary>
-    public string CurrentDvdTitle
+    public string CurrentTitle
     {
       get
       {
@@ -969,37 +962,14 @@ namespace MediaPortal.UI.Players.Video
           chapters.Add(GetChapterName(i));
       }
       lock (SyncObj)
-        _chapters = chapters.ToArray();
-    }
-
-    /// <summary>
-    /// Returns a localized chapter name.
-    /// </summary>
-    /// <param name="chapterNumber">0 based chapter number.</param>
-    /// <returns>Localized chapter name.</returns>
-    private static String GetChapterName(int chapterNumber)
-    {
-      //Idea: we could scrape chapter names and store them in MediaAspects. When they are available, return the full names here.
-      return ServiceRegistration.Get<ILocalization>().ToString(RES_PLAYBACK_CHAPTER, chapterNumber);
-    }
-
-    /// <summary>
-    /// Gets a list of available chapters.
-    /// </summary>
-    public string[] Chapters
-    {
-      get
-      {
-        lock (SyncObj)
-          return _chapters ?? EMPTY_STRING_ARRAY;
-      }
+        _chapterNames = chapters.ToArray();
     }
 
     /// <summary>
     /// Sets the chapter to play.
     /// </summary>
     /// <param name="chapter">Chapter name</param>
-    public void SetChapter(string chapter)
+    public override void SetChapter(string chapter)
     {
       string[] chapters = Chapters;
       for (int i = 0; i < chapters.Length; i++)
@@ -1015,7 +985,7 @@ namespace MediaPortal.UI.Players.Video
     /// <summary>
     /// Gets the current chapter.
     /// </summary>
-    public string CurrentChapter
+    public override string CurrentChapter
     {
       get
       {
@@ -1026,20 +996,9 @@ namespace MediaPortal.UI.Players.Video
     }
 
     /// <summary>
-    /// Indicate if chapters are available.
-    /// </summary>
-    public bool ChaptersAvailable
-    {
-      get
-      {
-        return Chapters.Length != 0;
-      }
-    }
-
-    /// <summary>
     /// Skip to next chapter.
     /// </summary>
-    public void NextChapter()
+    public override void NextChapter()
     {
       IDvdCmd cmd;
       _dvdCtrl.PlayNextChapter(DvdCmdFlags.None, out cmd);
@@ -1048,7 +1007,7 @@ namespace MediaPortal.UI.Players.Video
     /// <summary>
     /// Skip to previous chapter.
     /// </summary>
-    public void PrevChapter()
+    public override void PrevChapter()
     {
       IDvdCmd cmd;
       _dvdCtrl.PlayPrevChapter(DvdCmdFlags.None, out cmd);
diff --git a/MediaPortal/Source/UI/Players/VideoPlayer/VideoPlayer.cs b/MediaPortal/Source/UI/Players/VideoPlayer/VideoPlayer.cs
index ef57947..1d53aca 100644
--- a/MediaPortal/Source/UI/Players/VideoPlayer/VideoPlayer.cs
+++ b/MediaPortal/Source/UI/Players/VideoPlayer/VideoPlayer.cs
@@ -30,6 +30,7 @@ using System.Security;
 using System.Windows.Forms;
 using DirectShowLib;
 using MediaPortal.Common;
+using MediaPortal.Common.Localization;
 using MediaPortal.Common.Logging;
 using MediaPortal.Common.ResourceAccess;
 using MediaPortal.Common.Messaging;
@@ -48,7 +49,7 @@ using System.Globalization;
 
 namespace MediaPortal.UI.Players.Video
 {
-  public class VideoPlayer : ISlimDXVideoPlayer, IDisposable, IPlayerEvents, IInitializablePlayer, IMediaPlaybackControl, ISubtitlePlayer
+  public class VideoPlayer : ISlimDXVideoPlayer, IDisposable, IPlayerEvents, IInitializablePlayer, IMediaPlaybackControl, ISubtitlePlayer, IChapterPlayer
   {
     #region Classes & interfaces
 
@@ -91,6 +92,7 @@ namespace MediaPortal.UI.Players.Video
     protected const string NO_SUBTITLES = "No subtitles";
 
     protected const double PLAYBACK_RATE_PLAY_THRESHOLD = 0.05;
+    public const string RES_PLAYBACK_CHAPTER = "[Playback.Chapter]";
 
     #region Protected Properties
 
@@ -149,6 +151,16 @@ namespace MediaPortal.UI.Players.Video
     protected StreamInfoHandler _streamInfoSubtitles = null;
     private readonly object _syncObj = new object();
 
+    /// <summary>
+    /// List of chapters timestapms. Will be initialized lazily. <c>null</c> if not currently valid.
+    /// </summary>
+    protected double[] _chapterTimestamps = null;
+    
+    /// <summary>
+    /// List of chapters. Will be initialized lazily. <c>null</c> if not currently valid.
+    /// </summary>
+    protected string[] _chapterNames = null;
+
     protected bool _useTexture = true;
     protected bool _textureInvalid = true;
 
@@ -385,6 +397,7 @@ namespace MediaPortal.UI.Players.Video
     /// </summary>
     protected virtual void OnGraphRunning()
     {
+      EnumerateStreams();
       SetPreferredSubtitle();
       SetPreferredAudio();
     }
@@ -1011,7 +1024,7 @@ namespace MediaPortal.UI.Players.Video
         foreach (
           IAMStreamSelect streamSelector in FilterGraphTools.FindFiltersByInterface<IAMStreamSelect>(_graphBuilder))
         {
-          FilterInfo fi = FilterGraphTools.QueryFilterInfoAndFree(((IBaseFilter) streamSelector));
+          FilterInfo fi = FilterGraphTools.QueryFilterInfoAndFree(((IBaseFilter)streamSelector));
           int streamCount;
           streamSelector.Count(out streamCount);
 
@@ -1046,7 +1059,7 @@ namespace MediaPortal.UI.Players.Video
                   // if audio information is available via WaveEx format, query the channel count
                   if (mediaType.formatType == FormatType.WaveEx && mediaType.formatPtr != IntPtr.Zero)
                   {
-                    WaveFormatEx waveFormatEx = (WaveFormatEx) Marshal.PtrToStructure(mediaType.formatPtr, typeof(WaveFormatEx));
+                    WaveFormatEx waveFormatEx = (WaveFormatEx)Marshal.PtrToStructure(mediaType.formatPtr, typeof(WaveFormatEx));
                     streamAppendix = String.Format("{0} {1}ch", streamAppendix, waveFormatEx.nChannels);
                   }
                   currentStream.Name = String.Format("{0} ({1})", streamName, streamAppendix);
@@ -1064,6 +1077,30 @@ namespace MediaPortal.UI.Players.Video
             FilterGraphTools.FreeAMMediaType(mediaType);
           }
         }
+
+        //Try to find a filter implementing IAMExtendSeeking for chapter support
+        try
+        {
+          IAMExtendedSeeking extendSeeking = FilterGraphTools.FindFilterByInterface<IAMExtendedSeeking>(_graphBuilder);
+          if (extendSeeking != null)
+          {
+            int markerCount;
+            if (extendSeeking.get_MarkerCount(out markerCount) == 0 && markerCount > 0)
+            {
+              _chapterTimestamps = new double[markerCount];
+              _chapterNames = new string[markerCount];
+              for (int i = 1; i <= markerCount; i++)
+              {
+                double markerTime;
+                extendSeeking.GetMarkerTime(i, out markerTime);
+                _chapterTimestamps[i - 1] = markerTime;
+                _chapterNames[i - 1] = GetChapterName(i);
+              }
+            }
+          }
+        }
+        catch { }
+
         return true;
       }
       return false;
@@ -1244,7 +1281,6 @@ namespace MediaPortal.UI.Players.Video
 
     protected virtual void SetPreferredSubtitle()
     {
-      EnumerateStreams();
       if (_streamInfoSubtitles == null)
         return;
 
@@ -1328,6 +1364,130 @@ namespace MediaPortal.UI.Players.Video
 
     #endregion
 
+    #region IChapterPlayer implementation
+
+    /// <summary>
+    /// Gets a list of available chapters.
+    /// </summary>
+    public virtual string[] Chapters
+    {
+      get 
+      {
+        lock (SyncObj)
+          return _chapterNames ?? EMPTY_STRING_ARRAY; 
+      }
+    }
+
+    /// <summary>
+    /// Sets the chapter to play.
+    /// </summary>
+    /// <param name="chapter">Chapter name</param>
+    public virtual void SetChapter(string chapter)
+    {
+      string[] chapters = Chapters;
+      for (int i = 0; i < chapters.Length; i++)
+      {
+        if (chapter == chapters[i])
+        {
+          SetChapterByIndex(i);
+          return;
+        }
+      }
+    }
+
+    /// <summary>
+    /// Indicate if chapters are available.
+    /// </summary>
+    public virtual bool ChaptersAvailable
+    {
+      get { return _chapterNames != null; }
+    }
+
+    /// <summary>
+    /// Skip to next chapter.
+    /// </summary>
+    public virtual void NextChapter()
+    {
+      Int32 currentChapter;
+      if (GetCurrentChapter(out currentChapter))
+      {
+        SetChapterByIndex(currentChapter + 1);
+      }
+    }
+
+    /// <summary>
+    /// Skip to previous chapter.
+    /// </summary>
+    public virtual void PrevChapter()
+    {
+      Int32 currentChapter;
+      if (GetCurrentChapter(out currentChapter))
+      {
+        SetChapterByIndex(currentChapter - 1);
+      }
+    }
+
+    /// <summary>
+    /// Gets the current chapter.
+    /// </summary>
+    public virtual string CurrentChapter
+    {
+      get
+      {
+        Int32 currentChapter;
+        if (GetCurrentChapter(out currentChapter))
+        {
+          string currentChapterName = _chapterNames[currentChapter] ?? null;
+          return currentChapterName;
+        }
+        else return null;
+      }
+    }
+
+    /// <summary>
+    /// Gets the current chapter.
+    /// </summary>
+    protected bool GetCurrentChapter(out Int32 chapterIndex)
+    {
+      double currentTimestamp = CurrentTime.TotalSeconds;
+      for (int c = _chapterTimestamps.Length - 1; c >= 0; c--)
+      {
+        if (currentTimestamp > _chapterTimestamps[c])
+        {
+          chapterIndex = c;
+          return true;
+        }
+      }
+      chapterIndex = 0;
+      return false;
+    }
+
+    /// <summary>
+    /// Seek to the begining of the chapter to play
+    /// </summary>
+    /// <param name="chapterIndex">0 based chapter number.</param>
+    protected void SetChapterByIndex(Int32 chapterIndex)
+    {
+      if (chapterIndex > _chapterTimestamps.Length || chapterIndex < 0)
+        return;
+      TimeSpan seekTo = TimeSpan.FromSeconds(_chapterTimestamps[chapterIndex]);
+      CurrentTime = seekTo;
+      return;
+    }
+
+    /// <summary>
+    /// Returns a localized chapter name.
+    /// </summary>
+    /// <param name="chapterNumber">0 based chapter number.</param>
+    /// <returns>Localized chapter name.</returns>
+    protected static string GetChapterName(int chapterNumber)
+    {
+      //Idea: we could scrape chapter names and store them in MediaAspects. When they are available, return the full names here.
+      return ServiceRegistration.Get<ILocalization>().ToString(RES_PLAYBACK_CHAPTER, chapterNumber);
+    }
+
+    #endregion
+
     #region Base overrides
 
     public override string ToString()
@@ -1336,5 +1496,7 @@ namespace MediaPortal.UI.Players.Video
     }
 
     #endregion
+
+
   }
 }
diff --git a/MediaPortal/Source/UI/UiComponents/Media/Models/DefaultVideoPlayerUIContributor.cs b/MediaPortal/Source/UI/UiComponents/Media/Models/DefaultVideoPlayerUIContributor.cs
index accc34a..ea89548 100644
--- a/MediaPortal/Source/UI/UiComponents/Media/Models/DefaultVideoPlayerUIContributor.cs
+++ b/MediaPortal/Source/UI/UiComponents/Media/Models/DefaultVideoPlayerUIContributor.cs
@@ -45,6 +45,7 @@ namespace MediaPortal.UiComponents.Media.Models
     public DefaultVideoPlayerUIContributor() : base(300)
     {
       _subtitlesAvailableProperty = new WProperty(typeof(bool), false);
+      _chaptersAvailableProperty = new WProperty(typeof(bool), false);
       StartTimer();
     }
 
@@ -52,11 +53,18 @@ namespace MediaPortal.UiComponents.Media.Models
 
     #region Variables
 
-    protected ISubtitlePlayer _player;
+    protected ISubtitlePlayer _subtitlePlayer;
     protected AbstractProperty _subtitlesAvailableProperty;
     protected string[] _subtitles = EMPTY_STRING_ARRAY;
     protected ItemsList _subtitleMenuItems;
 
+    protected IChapterPlayer _chapterPlayer;
+    protected AbstractProperty _chaptersAvailableProperty;
+    protected string[] _chapters = EMPTY_STRING_ARRAY;
+    protected ItemsList _chapterMenuItems;
+
+    protected bool _updating;
+
     #endregion
 
     public AbstractProperty SubtitlesAvailableProperty
@@ -70,6 +78,17 @@ namespace MediaPortal.UiComponents.Media.Models
       set { _subtitlesAvailableProperty.SetValue(value); }
     }
 
+    public AbstractProperty ChaptersAvailableProperty
+    {
+      get { return _chaptersAvailableProperty; }
+    }
+
+    public bool ChaptersAvailable
+    {
+      get { return (bool)_chaptersAvailableProperty.GetValue(); }
+      set { _chaptersAvailableProperty.SetValue(value); }
+    }
+
     /// <summary>
     /// Provides a list of items to be shown in the subtitle selection menu.
     /// </summary>
@@ -78,7 +97,7 @@ namespace MediaPortal.UiComponents.Media.Models
       get
       {
         _subtitleMenuItems.Clear();
-        ISubtitlePlayer subtitlePlayer = _player;
+        ISubtitlePlayer subtitlePlayer = _subtitlePlayer;
         if (subtitlePlayer != null && _subtitles.Length > 0)
         {
           string currentSubtitle = subtitlePlayer.CurrentSubtitle;
@@ -102,6 +121,36 @@ namespace MediaPortal.UiComponents.Media.Models
       }
     }
 
+    /// <summary>
+    /// Provides a list of items to be shown in the chapter selection menu.
+    /// </summary>
+    public ItemsList ChapterMenuItems
+    {
+      get
+      {
+        string currentChapter = _chapterPlayer.CurrentChapter;
+        _chapterMenuItems.Clear();
+        if (ChaptersAvailable)
+        {
+          foreach (string chapter in _chapterPlayer.Chapters)
+          {
+            // use local variable, otherwise delegate argument is not fixed
+            string localChapter = chapter;
+
+            ListItem item = new ListItem(Consts.KEY_NAME, localChapter)
+            {
+              Command = new MethodDelegateCommand(() => _chapterPlayer.SetChapter(localChapter)),
+              // check if it is the selected chapter, then mark it
+              Selected = (localChapter == currentChapter)
+            };
+
+            _chapterMenuItems.Add(item);
+          }
+        }
+        return _chapterMenuItems;
+      }
+    }
+
 
     public bool BackgroundDisabled
     {
@@ -128,20 +177,40 @@ namespace MediaPortal.UiComponents.Media.Models
     public void Initialize(MediaWorkflowStateType stateType, IPlayer player)
     {
       _mediaWorkflowStateType = stateType;
-      _player = player as ISubtitlePlayer;
+      _subtitlePlayer = player as ISubtitlePlayer;
+      _chapterPlayer = player as IChapterPlayer;
       _subtitleMenuItems = new ItemsList();
+      _chapterMenuItems = new ItemsList();
     }
 
     // Update GUI properties
     protected override void Update()
     {
-      if (_player != null)
+      if (!_updating)
       {
-        _subtitles = _player.Subtitles;
-        SubtitlesAvailable = _subtitles.Length > 0;
+        try
+        {
+          if (_subtitlePlayer != null)
+          {
+            _subtitles = _subtitlePlayer.Subtitles;
+            SubtitlesAvailable = _subtitles.Length > 0;
+          }
+          else
+            _subtitles = EMPTY_STRING_ARRAY;
+
+          if (_chapterPlayer != null)
+          {
+            _chapters = _chapterPlayer.Chapters;
+            ChaptersAvailable = _chapterPlayer.ChaptersAvailable;
+          }
+          else
+            _chapters = EMPTY_STRING_ARRAY;
+        }
+        finally
+        {
+          _updating = false;
+        }
       }
-      else
-        _subtitles = EMPTY_STRING_ARRAY;
     }
 
     /// <summary>
@@ -153,6 +222,14 @@ namespace MediaPortal.UiComponents.Media.Models
     }
 
     /// <summary>
+    /// Opens the chapter selection dialog.
+    /// </summary>
+    public void OpenChooseChapterDialog()
+    {
+      ServiceRegistration.Get<IScreenManager>().ShowDialog("DialogChooseChapter");
+    }
+
+    /// <summary>
     /// Execute selected menu item for subtitle and chapter selection.
     /// </summary>
     /// <param name="item">One of the items of <see cref="SubtitleMenuItems"/>.</param>
@@ -171,5 +248,22 @@ namespace MediaPortal.UiComponents.Media.Models
       IPlayerContext pc = pcm.GetPlayerContext(PlayerManagerConsts.PRIMARY_SLOT);
       PlayerConfigurationDialogModel.OpenChooseGeometryDialog(pc);
     }
+
+    // <summary>
+    /// Skips to previous chapter.
+    /// </summary>
+    public void PrevChapter()
+    {
+      _chapterPlayer.PrevChapter();
+    }
+
+    /// <summary>
+    /// Skips to next chapter.
+    /// </summary>
+    public void NextChapter()
+    {
+      _chapterPlayer.NextChapter();
+    }
+
   }
 }
\ No newline at end of file
diff --git a/MediaPortal/Source/UI/UiComponents/Media/Skin/default/themes/default/styles/MediaStyles.xaml b/MediaPortal/Source/UI/UiComponents/Media/Skin/default/themes/default/styles/MediaStyles.xaml
index d0b6364..7de3d8d 100644
--- a/MediaPortal/Source/UI/UiComponents/Media/Skin/default/themes/default/styles/MediaStyles.xaml
+++ b/MediaPortal/Source/UI/UiComponents/Media/Skin/default/themes/default/styles/MediaStyles.xaml
@@ -137,7 +137,16 @@
                     Command="{Command SeekForward}"/>
                 <Button x:Name="SkipForwardButton" Style="{ThemeResource SkipForwardButtonStyle}" Margin="5,0,5,0" IsVisible="{Binding CanSkipForward}"
                     Command="{Command Next}"/>
-                <Button x:Name="SelectSubtitleButton" Style="{ThemeResource SelectSubtitleButtonStyle}" Margin="5,0,5,0" 
+                <Button x:Name="PreviousChapterButton" Style="{ThemeResource PreviousChapterButtonStyle}" Margin="5,0,5,0"
+                    IsVisible="{Binding Source={StaticResource VideoPlayerModel},Path=PlayerUIContributor.ChaptersAvailable}"
+                    Command="{Command Source={StaticResource VideoPlayerModel},Path=PlayerUIContributor.PrevChapter}"/>
+                  <Button x:Name="SelectChapterButton" Style="{ThemeResource SelectChapterButtonStyle}" Margin="5,0,5,0"
+                    IsVisible="{Binding Source={StaticResource VideoPlayerModel},Path=PlayerUIContributor.ChaptersAvailable}"
+                    Command="{Command Source={StaticResource VideoPlayerModel},Path=PlayerUIContributor.OpenChooseChapterDialog}"/>
+                  <Button x:Name="NextChapterButton" Style="{ThemeResource NextChapterButtonStyle}" Margin="5,0,5,0"
+                    IsVisible="{Binding Source={StaticResource VideoPlayerModel},Path=PlayerUIContributor.ChaptersAvailable}"
+                    Command="{Command Source={StaticResource VideoPlayerModel},Path=PlayerUIContributor.NextChapter}"/>
+                <Button x:Name="SelectSubtitleButton" Style="{ThemeResource SelectSubtitleButtonStyle}" Margin="5,0,5,0"
                     IsVisible="{Binding Source={StaticResource VideoPlayerModel},Path=PlayerUIContributor.SubtitlesAvailable}"
                     Command="{Command Source={StaticResource VideoPlayerModel},Path=PlayerUIContributor.OpenChooseSubtitleDialog}"/>
 
-- 
1.7.6.msysgit.0

