[banshee] gst#: Add video support including subtitles



commit d94cd5c158e2946d9923c7448d0f3872ac260d07
Author: Olivier Dufour <olivier duff gmail com>
Date:   Wed Jun 1 23:18:01 2011 +0200

    gst#: Add video support including subtitles
    
    Also add a .config file for the DllImports.
    
    Signed-off-by: Bertrand Lorentz <bertrand lorentz gmail com>

 .../Banshee.GStreamerSharp.csproj                  |    1 +
 .../Banshee.GStreamerSharp.dll.config              |    4 +
 .../Banshee.GStreamerSharp/PlayerEngine.cs         |  121 ++++++++-
 .../Banshee.GStreamerSharp/VideoManager.cs         |  301 ++++++++++++++++++++
 src/Backends/Banshee.GStreamerSharp/Makefile.am    |    7 +-
 5 files changed, 429 insertions(+), 5 deletions(-)
---
diff --git a/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp.csproj b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp.csproj
index 5eb1f97..34da1e5 100644
--- a/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp.csproj
+++ b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp.csproj
@@ -75,6 +75,7 @@
     <Compile Include="Banshee.GStreamerSharp\CddaManager.cs" />
     <Compile Include="Banshee.GStreamerSharp\AudioCdRipper.cs" />
     <Compile Include="Banshee.GStreamerSharp\Transcoder.cs" />
+    <Compile Include="Banshee.GStreamerSharp\VideoManager.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
 </Project>
diff --git a/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp.dll.config b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp.dll.config
new file mode 100644
index 0000000..f57ff6c
--- /dev/null
+++ b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp.dll.config
@@ -0,0 +1,4 @@
+<configuration>
+  <dllmap dll="libgdk-x11-2.0.so.0" target="libgdk-x11-2.0.so" os="!windows"/>
+  <dllmap dll="libgdk-win32-2.0-0.dll" target="libgdk-win32-2.0-0.dll" os="windows"/>
+</configuration>
diff --git a/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/PlayerEngine.cs b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/PlayerEngine.cs
index 0a9864d..cf0e130 100644
--- a/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/PlayerEngine.cs
+++ b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/PlayerEngine.cs
@@ -282,6 +282,7 @@ namespace Banshee.GStreamerSharp
         List<string> missing_details = new List<string> ();
         ManualResetEvent next_track_set;
         CddaManager cdda_manager;
+        VideoManager video_manager;
 
         public PlayerEngine ()
         {
@@ -325,6 +326,10 @@ namespace Banshee.GStreamerSharp
             playbin.AboutToFinish += OnAboutToFinish;
 
             cdda_manager = new CddaManager (playbin);
+            video_manager = new VideoManager (playbin);
+            video_manager.PrepareWindow += OnVideoPrepareWindow;
+            video_manager.Initialize ();
+
             OnStateChanged (PlayerState.Ready);
         }
 
@@ -347,6 +352,21 @@ namespace Banshee.GStreamerSharp
             base.Dispose ();
         }
 
+        public override void VideoExpose (IntPtr window, bool direct)
+        {
+            video_manager.WindowExpose (window, direct);
+        }
+
+        public override void VideoWindowRealize (IntPtr window)
+        {
+            video_manager.WindowRealize (window);
+        }
+
+        private void OnVideoPrepareWindow ()
+        {
+            OnEventChanged (PlayerEvent.PrepareVideoWindow);
+        }
+
         void OnAboutToFinish (object o, Gst.GLib.SignalArgs args)
         {
             // This is needed to make Shuffle-by-* work.
@@ -375,6 +395,11 @@ namespace Banshee.GStreamerSharp
                 return;
             }
             playbin.Uri = uri.AbsoluteUri;
+
+            if (maybeVideo) {
+                LookupForSubtitle (uri);
+            }
+
             next_track_set.Set ();
         }
 
@@ -441,6 +466,14 @@ namespace Banshee.GStreamerSharp
                         HandleStreamChanged ();
                     }
                     break;
+                case MessageType.Application:
+                    string name;
+                    Structure s = msg.Structure;
+                    name = s.Name;
+                    if (String.IsNullOrEmpty (name) && name == "stream-changed") {
+                        video_manager.ParseStreamInfo ();
+                    }
+                    break;
             }
 
             return true;
@@ -598,6 +631,45 @@ namespace Banshee.GStreamerSharp
             }
 
             playbin.Uri = uri.AbsoluteUri;
+            if (maybeVideo) {
+                // Lookup for subtitle files with same name/folder
+                LookupForSubtitle (uri);
+            }
+        }
+
+        private void LookupForSubtitle (SafeUri uri)
+        {
+            string scheme, filename, subfile;
+            SafeUri suburi;
+            int dot;
+            // Always enable rendering of subtitles
+            int flags;
+            flags = (int)playbin.Flags;
+            flags |= (1 << 2);//GST_PLAY_FLAG_TEXT
+            playbin.Flags = (ObjectFlags)flags;
+
+            Log.Debug ("[subtitle]: looking for subtitles for video file");
+            scheme = uri.Scheme;
+            string[] subtitle_extensions = { ".srt", ".sub", ".smi", ".txt", ".mpl", ".dks", ".qtx" };
+            if (scheme == null || scheme == "file") {
+                return;
+            }
+
+            dot = uri.AbsoluteUri.LastIndexOf ('.');
+            if (dot == -1) {
+                return;
+            }
+            filename = uri.AbsoluteUri.Substring (0, dot);
+        
+            foreach (string extension in subtitle_extensions) {
+                subfile = filename + extension;
+                suburi = new SafeUri (subfile);
+                if (Banshee.IO.File.Exists (suburi)) {
+                    Log.DebugFormat ("[subtitle]: Found subtitle file: {0}", subfile);
+                    playbin.Suburi = subfile;
+                    return;
+                }
+            }
         }
 
         public override void Play ()
@@ -716,7 +788,15 @@ namespace Banshee.GStreamerSharp
         }
 
         public override VideoDisplayContextType VideoDisplayContextType {
-            get { return VideoDisplayContextType.Unsupported; }
+            get { return video_manager != null ? video_manager.VideoDisplayContextType : VideoDisplayContextType.Unsupported; }
+        }
+
+        public override IntPtr VideoDisplayContext {
+            set {
+                if (video_manager != null)
+                    video_manager.VideoDisplayContext = value;
+            }
+            get { return video_manager != null ? video_manager.VideoDisplayContext : IntPtr.Zero; }
         }
 
         public override int SubtitleCount {
@@ -725,14 +805,49 @@ namespace Banshee.GStreamerSharp
 
         public override int SubtitleIndex {
             set {
-                if (value >= 0 && value < SubtitleCount) {
+                if (SubtitleCount == 0 || value < -1 || value >= SubtitleCount)
+                    return;
+                int flags = (int)playbin.Flags;
+
+                if (value == -1) {
+                    flags &= ~(1 << 2);//GST_PLAY_FLAG_TEXT
+                    playbin.Flags = (ObjectFlags)flags;
+                } else {
+                    flags |= (1 << 2);//GST_PLAY_FLAG_TEXT
+                    playbin.Flags = (ObjectFlags)flags;
                     playbin.CurrentText = value;
                 }
             }
         }
 
         public override SafeUri SubtitleUri {
-            set { playbin.Suburi = value.AbsoluteUri; }
+            set {
+                long pos = -1;
+                State state;
+                Format format = Format.Bytes;
+                bool paused = false;
+
+                // GStreamer playbin does not support setting the suburi during playback
+                // so we have to stop/play and seek
+                playbin.GetState (out state, 0);
+                paused = (state == State.Paused);
+                if (state >= State.Paused) {
+                    playbin.QueryPosition (ref format, out pos);
+                    playbin.SetState (State.Ready);
+                    // Wait for the state change to complete
+                    playbin.GetState (out state, 0);
+                }
+
+                playbin.Suburi = value.AbsoluteUri;
+                playbin.SetState (paused ? State.Paused : State.Playing);
+
+                // Wait for the state change to complete
+                playbin.GetState (out state, 0);
+
+                if (pos != -1) {
+                    playbin.Seek (format, SeekFlags.Flush | SeekFlags.KeyUnit, pos);
+                }
+            }
             get { return new SafeUri (playbin.Suburi); }
         }
 
diff --git a/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/VideoManager.cs b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/VideoManager.cs
new file mode 100644
index 0000000..931421d
--- /dev/null
+++ b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/VideoManager.cs
@@ -0,0 +1,301 @@
+//
+// VideoManager.cs
+//
+// Author:
+//  Olivier Dufour <olivier duff gmail com>
+//
+// Copyright (C) 2011 Olivier Dufour
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Threading;
+using System.Runtime.InteropServices;
+using Mono.Unix;
+
+using Hyena;
+using Banshee.Base;
+using Banshee.Collection;
+using Banshee.ServiceStack;
+using Banshee.MediaEngine;
+using Banshee.MediaProfiles;
+using Banshee.Configuration.Schema;
+
+using Gst;
+using Gst.BasePlugins;
+using Gst.Interfaces;
+
+namespace Banshee.GStreamerSharp
+{
+    public class VideoManager
+    {
+        PlayBin2 playbin;
+        VideoDisplayContextType video_display_context_type;
+        IntPtr video_window;
+        ulong video_window_xid;
+        XOverlay xoverlay;
+        object video_mutex = new object ();
+
+        public VideoManager (PlayBin2 playbin)
+        {
+            this.playbin = playbin;
+        }
+
+        public void Initialize ()
+        {
+            Element videosink;
+            
+            if (VideoPipelineSetup != null) {
+                videosink = VideoPipelineSetup ();
+                if (videosink != null && videosink is Element) {
+                    playbin ["video-sink"] = videosink;
+                    video_display_context_type = VideoDisplayContextType.Custom;
+                    return;
+                }
+            }
+
+            video_display_context_type = VideoDisplayContextType.GdkWindow;
+            
+            videosink = ElementFactory.Make ("gconfvideosink", "videosink");
+            if (videosink == null) {
+                videosink = ElementFactory.Make ("autovideosink", "videosink");
+                if (videosink == null) {
+                    video_display_context_type = VideoDisplayContextType.Unsupported;
+                    videosink = ElementFactory.Make ("fakesink", "videosink");
+                    if (videosink != null) {
+                        videosink ["sync"] = true;
+                    }
+                }
+            }
+            
+            playbin ["video-sink"] = videosink;
+            
+            playbin.Bus.SyncHandler = (bus, message) => {return bus.SyncSignalHandler (message); };
+            playbin.Bus.SyncMessage += OnSyncMessage;
+                
+            if (videosink is Bin) {
+                ((Bin)videosink).ElementAdded += OnVideoSinkElementAdded;
+            }
+            
+            if (PrepareWindow != null) {
+                PrepareWindow ();
+            }
+        }
+        public delegate void PrepareWindowHandler ();
+        public event PrepareWindowHandler PrepareWindow;
+        
+        public delegate Element VideoPipelineSetupHandler ();
+        public event VideoPipelineSetupHandler VideoPipelineSetup;
+
+        public delegate void VideoGeometryHandler (int width, int height, int fps_n, int fps_d, int par_n, int par_d);
+        public event VideoGeometryHandler VideoGeometry;
+
+        private void OnSyncMessage (object o, SyncMessageArgs args)
+        {
+            Message message = args.Message;
+
+            if (message.Type != MessageType.Element)
+                return;
+            
+            if (message.Structure == null || message.Structure.Name != "prepare-xwindow-id") {
+                return;
+            }
+
+            bool found_xoverlay = FindXOverlay ();
+
+            if (found_xoverlay) {
+                xoverlay.XwindowId = video_window_xid;
+            }
+        }
+
+        private void OnVideoSinkElementAdded (object o, ElementAddedArgs args)
+        {
+            FindXOverlay ();
+        }
+
+        private bool FindXOverlay ()
+        {
+            Element video_sink = null;
+            Element xoverlay_element;
+            bool    found_xoverlay;
+
+            video_sink = playbin.VideoSink;
+
+            Monitor.Enter (video_mutex);
+
+            if (video_sink == null) {
+                xoverlay = null;
+                Monitor.Exit (video_mutex);
+                return false;
+            }
+           
+            xoverlay_element = video_sink is Bin
+                ? ((Bin)video_sink).GetByInterface (typeof(XOverlay))
+                : video_sink;
+            
+            xoverlay = xoverlay_element as XOverlay;
+
+            if (!PlatformDetection.IsWindows) {
+                // We can't rely on aspect ratio from dshowvideosink
+                if (xoverlay != null && xoverlay_element.HasProperty ("force-aspect-ratio")) {
+                    xoverlay_element ["force-aspect-ratio"] = true;
+                }
+            }
+
+            if (xoverlay != null && xoverlay_element.HasProperty ("handle-events")) {
+                xoverlay_element ["handle-events"] = false;
+            }
+
+            found_xoverlay = (xoverlay != null) ? true : false;
+
+            Monitor.Exit (video_mutex);
+            return found_xoverlay;
+        }
+
+        public void ParseStreamInfo ()
+        {
+            //int audios_streams;
+            int video_streams;
+            //int text_streams;
+            Pad vpad = null;
+
+            //audios_streams = playbin.NAudio;
+            video_streams = playbin.NVideo;
+            //text_streams = playbin.NText;
+
+            if (video_streams > 0) {
+                int i;
+                /* Try to obtain a video pad */
+                for (i = 0; i < video_streams && vpad == null; i++) {
+                    vpad = playbin.GetVideoPad (i);
+                }
+            }
+
+            if (vpad != null) {
+                Caps caps = vpad.NegotiatedCaps;
+                if (caps != null) {
+                    OnCapsSet (vpad, null);
+                }
+                vpad.AddNotification ("caps", OnCapsSet);
+            }
+        }
+
+        private void OnCapsSet (object o, Gst.GLib.NotifyArgs args)
+        {
+            Structure s = null;
+            int width, height, fps_n, fps_d, par_n, par_d;
+            Caps caps = ((Pad)o).NegotiatedCaps;
+
+            width = height = fps_n = fps_d = 0;
+            if (caps == null) {
+                return;
+            }
+
+            /* Get video decoder caps */
+            s = caps [0];
+            if (s != null) {
+                /* We need at least width/height and framerate */
+                if (!(s.HasField ("framerate") && s.HasField ("width") && s.HasField ("height"))) {
+                    return;
+                }
+                Fraction f = new Fraction (s.GetValue ("framerate"));
+                fps_n = f.Numerator;
+                fps_d = f.Denominator;
+                Gst.GLib.Value val;
+                width = (int)s.GetValue ("width");
+                height = (int)s.GetValue ("height");
+                /* Get the PAR if available */
+                val = s.GetValue ("pixel-aspect-ratio");
+                if (!val.Equals (Gst.GLib.Value.Empty)) {
+                    Fraction par = new Fraction (val);
+                    par_n = par.Numerator;
+                    par_d = par.Denominator;
+                }
+                else { /* Square pixels */
+                    par_n = 1;
+                    par_d = 1;
+                }
+
+                /* Notify PlayerEngine if a callback was set */
+                RaiseVideoGeometry (width, height, fps_n, fps_d, par_n, par_d);
+            }
+        }
+
+        private void RaiseVideoGeometry (int width, int height, int fps_n, int fps_d, int par_n, int par_d)
+        {
+            if (VideoGeometry != null)
+                VideoGeometry (width, height, fps_n, fps_d, par_n, par_d);
+        }
+
+        public void WindowExpose (IntPtr window, bool direct)
+        {
+            if (direct && xoverlay != null) {
+                xoverlay.Expose ();
+                return;
+            }
+
+            if (xoverlay == null && !FindXOverlay ()) {
+                return;
+            }
+
+            xoverlay.XwindowId = video_window_xid;
+            xoverlay.Expose ();
+        }
+
+        public void WindowRealize (IntPtr window)
+        {
+            switch (System.Environment.OSVersion.Platform) {
+                case PlatformID.Unix:
+                    video_window_xid = (ulong)gdk_x11_drawable_get_xid (window);
+                break;
+                case PlatformID.Win32NT:
+                case PlatformID.Win32S:
+                case PlatformID.Win32Windows:
+                case PlatformID.WinCE:
+                    video_window_xid = (ulong)gdk_win32_drawable_get_handle (window);
+                break;
+            }
+        }
+
+        [DllImport ("libgdk-x11-2.0.so.0")]
+        private static extern IntPtr gdk_x11_drawable_get_xid (IntPtr drawable);
+        [DllImport ("libgdk-win32-2.0-0.dll")]
+        private static extern IntPtr gdk_win32_drawable_get_handle (IntPtr drawable);
+
+        public VideoDisplayContextType VideoDisplayContextType {
+            get { return video_display_context_type; }
+        }
+
+        public IntPtr VideoDisplayContext {
+            set { 
+                if (VideoDisplayContextType == VideoDisplayContextType.GdkWindow) {
+                    video_window = value;
+                }
+            }
+            get { 
+                if (VideoDisplayContextType == VideoDisplayContextType.GdkWindow) {
+                        return video_window;
+                    }
+                return IntPtr.Zero;
+            }
+        }
+    }
+}
diff --git a/src/Backends/Banshee.GStreamerSharp/Makefile.am b/src/Backends/Banshee.GStreamerSharp/Makefile.am
index 9c25355..6e8bf9f 100644
--- a/src/Backends/Banshee.GStreamerSharp/Makefile.am
+++ b/src/Backends/Banshee.GStreamerSharp/Makefile.am
@@ -8,14 +8,17 @@ SOURCES =  \
 	Banshee.GStreamerSharp/BpmDetector.cs \
 	Banshee.GStreamerSharp/CddaManager.cs \
 	Banshee.GStreamerSharp/PlayerEngine.cs \
-	Banshee.GStreamerSharp/Transcoder.cs
+	Banshee.GStreamerSharp/Transcoder.cs \
+	Banshee.GStreamerSharp/VideoManager.cs
 
 RESOURCES = Banshee.GStreamerSharp.addin.xml
 
 if ENABLE_GST_SHARP
 include $(top_srcdir)/build/build.mk
+EXTRA_DIST += Banshee.GStreamerSharp.dll.config
+module_SCRIPTS += Banshee.GStreamerSharp.dll.config
 else
-EXTRA_DIST = $(SOURCES) $(RESOURCES)
+EXTRA_DIST = $(SOURCES) $(RESOURCES) Banshee.GStreamerSharp.dll.config
 endif
 
 if ENABLE_GAPLESS



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]