2014-08-03 15:41:31 +00:00
// Authors
// Copyright (C) 2014 Stephan Sundermann <stephansundermann@gmail.com>
using System ;
using Gst ;
using Gtk ;
using System.Runtime.InteropServices ;
using Gst.Video ;
namespace GstreamerSharp
{
class Playback
{
static Element Playbin ;
static Range Slider ;
static TextView StreamsList ;
static ulong silderUpdateSignalID ;
static State State ;
static long Duration = - 1 ;
static int ignoreCount = 0 ;
static void HandleValueChanged ( object sender , EventArgs e )
{
var range = ( Range ) sender ;
var value = range . Value ;
Playbin . SeekSimple ( Format . Time , SeekFlags . Flush | SeekFlags . KeyUnit , ( long ) ( value * Gst . Constants . SECOND ) ) ;
}
// This method is called when the STOP button is clicked
static void HandleStop ( object sender , EventArgs e )
{
Playbin . SetState ( State . Ready ) ;
}
// This method is called when the PAUSE button is clicked
static void HandlePause ( object sender , EventArgs e )
{
Playbin . SetState ( State . Paused ) ;
}
// This method is called when the PLAY button is clicked
static void HandlePlay ( object sender , EventArgs e )
{
Playbin . SetState ( State . Playing ) ;
}
static void HandleRealized ( object sender , EventArgs e )
{
var widget = ( Widget ) sender ;
var window = widget . Window ;
2015-05-28 13:57:16 +00:00
IntPtr windowID = IntPtr . Zero ;
2014-08-03 15:41:31 +00:00
// Retrieve window handler from GDK
switch ( System . Environment . OSVersion . Platform ) {
case PlatformID . Unix :
windowID = gdk_x11_window_get_xid ( window . Handle ) ;
break ;
case PlatformID . Win32NT :
case PlatformID . Win32S :
case PlatformID . Win32Windows :
case PlatformID . WinCE :
2015-05-28 13:57:16 +00:00
windowID = gdk_win32_drawable_get_handle ( window . Handle ) ;
2014-08-03 15:41:31 +00:00
break ;
}
Element overlay = null ;
if ( Playbin is Gst . Bin )
overlay = ( ( Gst . Bin ) Playbin ) . GetByInterface ( VideoOverlayAdapter . GType ) ;
VideoOverlayAdapter adapter = new VideoOverlayAdapter ( overlay . Handle ) ;
adapter . WindowHandle = windowID ;
adapter . HandleEvents ( true ) ;
}
// This function is called when the main window is closed
static void HandleDelete ( object o , DeleteEventArgs args )
{
HandleStop ( null , null ) ;
Gtk . Application . Quit ( ) ;
}
//This function is called everytime the video window needs to be redrawn (due to damage/exposure, rescaling, etc). GStreamer takes care of this in the PAUSED and PLAYING states, otherwise, we simply draw a black rectangle to avoid garbage showing up. */
static void HandleDamage ( object o , DamageEventArgs args )
{
var widget = ( Widget ) o ;
if ( State ! = State . Paused & & State ! = State . Playing ) {
var window = widget . Window ;
var allocation = widget . Allocation ;
var cr = Gdk . CairoHelper . Create ( window ) ;
cr . SetSourceRGB ( 0 , 0 , 0 ) ;
cr . Rectangle ( 0 , 0 , allocation . Width , allocation . Height ) ;
cr . Fill ( ) ;
cr . Dispose ( ) ;
}
args . RetVal = false ;
}
static void CreateUI ( ) {
var mainWindow = new Window ( WindowType . Toplevel ) ;
mainWindow . DeleteEvent + = HandleDelete ;
var videoWindow = new DrawingArea ( ) ;
videoWindow . DoubleBuffered = false ;
videoWindow . Realized + = HandleRealized ;
videoWindow . DamageEvent + = HandleDamage ;
var playButton = new Button ( Stock . MediaPlay ) ;
playButton . Clicked + = HandlePlay ;
var pauseButton = new Button ( Stock . MediaPause ) ;
pauseButton . Clicked + = HandlePause ;
var stopButton = new Button ( Stock . MediaStop ) ;
stopButton . Clicked + = HandleStop ;
Slider = new HScale ( 0 , 100 , 1 ) ;
( ( Scale ) Slider ) . DrawValue = false ;
Slider . ValueChanged + = HandleValueChanged ;
StreamsList = new TextView ( ) ;
StreamsList . Editable = false ;
var controls = new HBox ( false , 0 ) ;
controls . PackStart ( playButton , false , false , 2 ) ;
controls . PackStart ( pauseButton , false , false , 2 ) ;
controls . PackStart ( stopButton , false , false , 2 ) ;
controls . PackStart ( Slider , true , true , 2 ) ;
var mainHBox = new HBox ( false , 0 ) ;
mainHBox . PackStart ( videoWindow , true , true , 0 ) ;
mainHBox . PackStart ( StreamsList , false , false , 2 ) ;
var mainBox = new VBox ( false , 0 ) ;
mainBox . PackStart ( mainHBox , true , true , 0 ) ;
mainBox . PackStart ( controls , false , false , 0 ) ;
mainWindow . Add ( mainBox ) ;
mainWindow . SetDefaultSize ( 640 , 480 ) ;
mainWindow . ShowAll ( ) ;
}
// This function is called periodically to refresh the GUI
static bool RefreshUI ( ) {
var fmt = Format . Time ;
long current = 0 ;
// We do not want to update anything nless we are in the PAUSED or PLAYING states
if ( State ! = State . Playing & & State ! = State . Paused )
return true ;
// If we didn't know it yet, query the stream duration
if ( Duration < 0 ) {
if ( ! Playbin . QueryDuration ( fmt , out Duration ) )
Console . WriteLine ( "Could not query the current duration." ) ;
else {
// Set the range of the silder to the clip duration, in SECONDS
Slider . SetRange ( 0 , Duration / ( double ) Gst . Constants . SECOND ) ;
}
}
if ( Playbin . QueryPosition ( fmt , out current ) ) {
// Block the "value-changed" signal, so the HandleSlider function is not called (which would trigger a seek the user has not requested)
ignoreCount + + ;
Slider . ValueChanged - = HandleValueChanged ;
// Set the position of the slider to the current pipeline position, in SECONDS
Slider . Value = current / ( double ) Gst . Constants . SECOND ;
Slider . ValueChanged + = HandleValueChanged ;
}
return true ;
}
// This function is called when an error message is posted on the bus
static void HandleTags ( object sender , GLib . SignalArgs args ) {
// We are possibly in the Gstreamer working thread, so we notify the main thread of this event through a message in the bus
var s = new Structure ( "tags-changed" ) ;
Playbin . PostMessage ( new Message ( Playbin , s ) ) ;
}
// This function is called when an error message is posted on the bus
static void HandleError ( object sender , GLib . SignalArgs args ) {
var msg = ( Message ) args . Args [ 0 ] ;
string debug ;
GLib . GException exc ;
msg . ParseError ( out exc , out debug ) ;
Console . WriteLine ( string . Format ( "Error received from element {0}: {1}" , msg . Src . Name , exc . Message ) ) ;
Console . WriteLine ( "Debugging information: {0}" , debug ) ;
// Set the pipeline to READY (which stops playback)
Playbin . SetState ( State . Ready ) ;
}
// This function is called when an End-Of-Stream message is posted on the bus. We just set the pipelien to READY (which stops playback)
static void HandleEos ( object sender , GLib . SignalArgs args ) {
Console . WriteLine ( "End-Of-Stream reached." ) ;
Playbin . SetState ( State . Ready ) ;
}
// This function is called when the pipeline changes states. We use it to keep track of the current state.
static void HandleStateChanged ( object sender , GLib . SignalArgs args ) {
var msg = ( Message ) args . Args [ 0 ] ;
State oldState , newState , pendingState ;
msg . ParseStateChanged ( out oldState , out newState , out pendingState ) ;
if ( msg . Src = = Playbin ) {
State = newState ;
Console . WriteLine ( "State set to {0}" , Element . StateGetName ( newState ) ) ;
if ( oldState = = State . Ready & & newState = = State . Paused ) {
// For extra responsiveness, we refresh the GUI as soon as we reach the PAUSED state
RefreshUI ( ) ;
}
}
}
// Extract metadata from all the streams and write it to the text widget in the GUI
static void AnalyzeStreams ( ) {
TagList tags ;
String str , totalStr ;
uint rate ;
// Clean current contents of the widget
var text = StreamsList . Buffer ;
text . Text = String . Empty ;
// Read some properties
var nVideo = ( int ) Playbin [ "n-video" ] ;
var nAudio = ( int ) Playbin [ "n-audio" ] ;
var nText = ( int ) Playbin [ "n-text" ] ;
for ( int i = 0 ; i < nVideo ; i + + ) {
// Retrieve the stream's video tags
tags = ( TagList ) Playbin . Emit ( "get-video-tags" , i ) ;
if ( tags ! = null ) {
totalStr = string . Format ( "video stream {0}:\n" , i ) ;
text . InsertAtCursor ( totalStr ) ;
tags . GetString ( Gst . Constants . TAG_VIDEO_CODEC , out str ) ;
totalStr = string . Format ( " codec: {0}\n" , str ! = null ? str : "unknown" ) ;
text . InsertAtCursor ( totalStr ) ;
}
}
for ( int i = 0 ; i < nAudio ; i + + ) {
// Retrieve the stream's audio tags
tags = ( TagList ) Playbin . Emit ( "get-audio-tags" , i ) ;
if ( tags ! = null ) {
totalStr = string . Format ( "audio stream {0}:\n" , i ) ;
text . InsertAtCursor ( totalStr ) ;
str = String . Empty ;
if ( tags . GetString ( Gst . Constants . TAG_AUDIO_CODEC , out str ) ) {
totalStr = string . Format ( " codec: {0}\n" , str ) ;
text . InsertAtCursor ( totalStr ) ;
}
str = String . Empty ;
if ( tags . GetString ( Gst . Constants . TAG_LANGUAGE_CODE + "dr" , out str ) ) {
totalStr = string . Format ( " language: {0}\n" , str ) ;
text . InsertAtCursor ( totalStr ) ;
}
str = String . Empty ;
if ( tags . GetUint ( Gst . Constants . TAG_BITRATE , out rate ) ) {
totalStr = string . Format ( " bitrate: {0}\n" , rate ) ;
text . InsertAtCursor ( totalStr ) ;
}
}
}
for ( int i = 0 ; i < nText ; i + + ) {
// Retrieve the stream's text tags
tags = ( TagList ) Playbin . Emit ( "get-text-tags" , i ) ;
if ( tags ! = null ) {
totalStr = string . Format ( "subtitle stream {0}:\n" , i ) ;
text . InsertAtCursor ( totalStr ) ;
if ( tags . GetString ( Gst . Constants . TAG_LANGUAGE_CODE , out str ) ) {
totalStr = string . Format ( " language: {0}\n" , str ) ;
text . InsertAtCursor ( totalStr ) ;
}
}
}
}
// This function is called when an "application" message is posted on the bus. Here we retrieve the message posted by the HandleTags callback
static void HandleApplication ( object sender , GLib . SignalArgs args ) {
var msg = ( Message ) args . Args [ 0 ] ;
if ( msg . Structure . Name . Equals ( "tags-changed" ) ) {
// If the message is the "tags-changed" (only one we are currently issuing), update the stream info GUI
AnalyzeStreams ( ) ;
}
}
public static void Main ( string [ ] args )
{
// Initialize GTK
Gtk . Application . Init ( ) ;
// Initialize Gstreamer
Gst . Application . Init ( ref args ) ;
// Create the elements
Playbin = ElementFactory . Make ( "playbin" , "playbin" ) ;
if ( Playbin = = null ) {
Console . WriteLine ( "Not all elements could be created" ) ;
return ;
}
// Set the URI to play.
2014-08-05 09:51:37 +00:00
Playbin [ "uri" ] = "http://download.blender.org/durian/trailer/sintel_trailer-1080p.mp4" ;
2014-08-03 15:41:31 +00:00
// Connect to interesting signals in playbin
Playbin . Connect ( "video-tags-changed" , HandleTags ) ;
Playbin . Connect ( "audio-tags-changed" , HandleTags ) ;
Playbin . Connect ( "text-tags-changed" , HandleTags ) ;
// Create the GUI
CreateUI ( ) ;
// Instruct the bus to emit signals for each received message, and connect to the interesting signals
var bus = Playbin . Bus ;
bus . AddSignalWatch ( ) ;
bus . Connect ( "message::error" , HandleError ) ;
bus . Connect ( "message::eos" , HandleEos ) ;
bus . Connect ( "message::state-changed" , HandleStateChanged ) ;
bus . Connect ( "message::application" , HandleApplication ) ;
// Start playing
var ret = Playbin . SetState ( State . Playing ) ;
if ( ret = = StateChangeReturn . Failure ) {
Console . WriteLine ( "Unable to set the pipeline to the playing state." ) ;
return ;
}
// Register a function that GLib will call every second
GLib . Timeout . Add ( 1 , RefreshUI ) ;
// Start the GTK main loop- We will not regain control until gtk_main_quit is called
Gtk . Application . Run ( ) ;
// Free resources
Playbin . SetState ( State . Null ) ;
}
[DllImport ("libgdk-3.so.0") ]
2015-05-28 13:57:16 +00:00
static extern IntPtr gdk_x11_window_get_xid ( IntPtr handle ) ;
2014-08-03 15:41:31 +00:00
[DllImport ("libgdk-win32-3.0-0.dll") ]
static extern IntPtr gdk_win32_drawable_get_handle ( IntPtr handle ) ;
[DllImport ("libX11.so.6")]
static extern int XInitThreads ( ) ;
}
2015-05-28 13:57:16 +00:00
}