1 /* 2 * gst_mediaplayer is placed in the 3 * public domain. 4 */ 5 6 module gst_mediaplayer; 7 8 //Tango imports 9 import tango.util.log.Trace;//Thread safe console output. 10 import Util = tango.text.Util; 11 import Integer = tango.text.convert.Integer; 12 import Stringz = tango.stdc.stringz; 13 14 import tango.sys.Environment; 15 import tango.io.FilePath; 16 import tango.io.Path; 17 18 //gtkD imports: 19 import gtk.Main; 20 import gtk.MainWindow; 21 22 import gtk.Widget; 23 import gdk.Drawable; 24 import gdk.Window; 25 import gdk.Event; 26 import gtk.DrawingArea; 27 import gtk.AspectFrame; 28 29 import gtk.FileChooserDialog; 30 31 import gdk.X11;//Needed for XOverlay 32 33 import gtk.VBox; 34 import gtk.HBox; 35 import gtk.Button; 36 import gtk.ComboBox; 37 38 import gobject.Value; 39 40 //gstreamerD imports: 41 42 import gstreamer.gstreamer; 43 44 import gobject.ObjectG; 45 import glib.ErrorG; 46 import gstreamer.Element; 47 import gstreamer.Bin; 48 import gstreamer.Pipeline; 49 import gstreamer.ElementFactory; 50 import gstreamer.Pad; 51 import gstreamer.Message; 52 import gstreamer.Structure; 53 import gstreamer.Bus; 54 55 import gstreamerc.gstreamertypes; 56 import gstreamerc.gstreamer; 57 58 import gstreamerc.gstinterfacestypes;//For GstXOverlay* 59 60 import gstinterfaces.XOverlay; 61 62 import gtkc.glib; 63 import gtkc.gobject; 64 65 66 class MonitorOverlay : public DrawingArea 67 { 68 public: 69 70 this() 71 { 72 debug(MonitorOverlay) Trace.formatln("Monitor.this() START."); 73 debug(MonitorOverlay) scope(exit) Trace.formatln("Monitor.this() END."); 74 75 setAppPaintable( true ); 76 77 //For some reason this does more harm than good? 78 //addOnExpose( &onExpose ); 79 } 80 81 /* 82 //For some reason this does more harm than good? 83 int onExpose(GdkEventExpose* event, Widget widget) 84 { 85 if( xoverlay !is null ) 86 { 87 //Trace.formatln("And we even have an xoverlay"); 88 xoverlay.expose();//For some reason this does more harm than good? 89 } 90 return false;//? 91 } 92 */ 93 94 public XOverlay xoverlay() { return m_xoverlay; } 95 /** 96 * Give this method an XOverlay that you've created from 97 * a videoSink. like: monitorOverlay.xoverlay = new XOverlay( videoSink ); 98 */ 99 public XOverlay xoverlay(XOverlay set) 100 { 101 debug(MonitorOverlay) Trace.formatln("Monitor.xoverlay(set) START."); 102 debug(MonitorOverlay) scope(exit) Trace.formatln("Monitor.xoverlay(set) END."); 103 m_xoverlay = set; 104 105 debug(MonitorOverlay) Trace.formatln("Monitor.xoverlay(set) xoverlay set. Now setting XwindowId."); 106 m_xoverlay.setXwindowId( X11.drawableGetXid( getWindow() ) ); 107 108 debug(MonitorOverlay) Trace.formatln("X11.drawableGetXid: {}", X11.drawableGetXid( getWindow() ) ); 109 110 return m_xoverlay; 111 } 112 protected XOverlay m_xoverlay; 113 114 } 115 116 117 class GstMediaPlayer : public MainWindow 118 { 119 public: 120 121 GstBusSyncReply createXOverlayWindowCb( Message msg ) 122 { 123 // ignore anything but 'prepare-xwindow-id' element messages 124 if( msg.type() != GstMessageType.ELEMENT ) 125 //if (GST_MESSAGE_TYPE (message) != GST_MESSAGE_ELEMENT) 126 return GstBusSyncReply.PASS; 127 128 /*GstMessageType typ = msg.type(); 129 Stdout("The message type is: ")( cast(int)typ ).newline; 130 131 ObjectGst obu = msg.src(); 132 Stdout("The message source is named: ")( obu.getName() ).newline; 133 */ 134 Structure str = msg.structure(); 135 if( str.hasName("prepare-xwindow-id") == false ) 136 //if (!gst_structure_has_name (message->structure, "prepare-xwindow-id")) 137 return GstBusSyncReply.PASS; 138 139 debug(MonitorOverlay) Trace.formatln("Now we should create the X window."); 140 141 monitorOverlay.xoverlay = new XOverlay( videosink ); 142 143 debug(MonitorOverlay) Trace.formatln("Created an xoverlay."); 144 145 return GstBusSyncReply.DROP; 146 } 147 148 bool busCall( Message msg ) 149 { 150 debug(gstreamer) Trace.formatln("GstMediaPlayer.busCall(msg) START."); 151 debug(gstreamer) scope(exit) Trace.formatln("GstMediaPlayer.busCall(msg) END."); 152 153 switch( msg.type ) 154 { 155 case GstMessageType.UNKNOWN: 156 Trace.formatln("Unknown message type."); 157 break; 158 case GstMessageType.EOS: 159 Trace.formatln("End-of-stream. Looping from the start."); 160 //Main.quit(); 161 onSeekToStart(null); 162 break; 163 164 case GstMessageType.ERROR: 165 { 166 string dbug; 167 ErrorG err; 168 msg.parseError(err, dbug); 169 //g_free (dbug); 170 Trace.formatln("Error: {} dbug: {}", Stringz.fromStringz(err.getErrorGStruct().message), dbug ); 171 //g_error_free (err); 172 Main.quit(); 173 break; 174 } 175 default: 176 break; 177 } 178 179 return true; 180 } 181 182 char[] g_appDir; 183 184 this(char[][] args) 185 { 186 187 super("GstMediaPlayer"); 188 189 setSizeRequest(600, 400); 190 191 vbox = new VBox(false,0); 192 193 monitorOverlay = new MonitorOverlay(); 194 monitorAspectFrame = new AspectFrame("", 0.5, 0.5, 16.0/9.0, false ); 195 196 monitorAspectFrame.add( monitorOverlay ); 197 monitorAspectFrame.setShadowType( GtkShadowType.NONE ); 198 monitorAspectFrame.setLabelWidget( null );//Yes! This get's rid of that stupid label on top of the aspectframe! More room for the monitor. 199 monitorAspectFrame.setBorderWidth(0);//No effect. Trying to get rid of that one pixel border... 200 monitorAspectFrame.setSizeRequest(360, -1);//No effect. Why? 201 202 vbox.packStart( monitorAspectFrame, true, true, 0 ); 203 204 buttonsHBox = new HBox(false,0); 205 206 openButton = new Button("Open..."); 207 openButton.addOnClicked( &onOpen ); 208 buttonsHBox.packStart( openButton, false, false, 0 ); 209 210 playButton = new Button("Play"); 211 playButton.addOnClicked( &onPlay ); 212 buttonsHBox.packStart( playButton, false, false, 0 ); 213 214 aspectComboBox = new ComboBox(true);//Create a new text ComboBox. 215 aspectComboBox.appendText("16:9"); 216 aspectComboBox.appendText("4:3"); 217 aspectComboBox.setActiveText("16:9"); 218 aspectComboBox.addOnChanged( &onAspectComboBoxChanged ); 219 220 buttonsHBox.packStart( aspectComboBox, false, false, 0 ); 221 222 quitButton = new Button(StockID.QUIT); 223 quitButton.addOnClicked( &onQuit ); 224 buttonsHBox.packStart( quitButton, false, false, 0 ); 225 226 vbox.packStart( buttonsHBox, false, false, 0 ); 227 228 add( vbox ); 229 230 showAll(); 231 232 scope mypath = new FilePath( args[0] ); 233 234 bool remove_trailing_dotslash = false; 235 236 g_appDir = mypath.path(); 237 238 Trace.formatln("g_appDir before: {}", g_appDir ); 239 240 if( g_appDir == "./" ) 241 { 242 remove_trailing_dotslash = true; 243 } 244 245 bool starts_with_two_dots = false; 246 if( args[0][0] == '.' && args[0][1] == '.' ) 247 starts_with_two_dots = true; 248 249 mypath = mypath.absolute(Environment.cwd());//This will add /home/user... 250 if( starts_with_two_dots ) 251 g_appDir = normalize( mypath.path() );//This will get rid of the trailing /../ 252 else g_appDir = mypath.path(); 253 254 if( remove_trailing_dotslash == true ) 255 { 256 //This will get rid of the trailing ./ 257 g_appDir = g_appDir[0..length-2]; 258 } 259 260 Trace.formatln("g_appDir after: {}", g_appDir ); 261 262 if (args.length > 1) 263 { 264 mediaFileUri = args[1]; 265 266 //This will construct the filename to be a URI, but it will only 267 //work for files in the same directory. 268 if( mediaFileUri[0..7] != "file://" && mediaFileUri[0..7] != "http://" ) 269 mediaFileUri = "file://" ~ g_appDir ~ mediaFileUri; 270 } 271 272 /* 273 // check input arguments 274 if (args.length > 1) 275 { 276 for( uint i = 0; i < args.length; i++ ) 277 { 278 char[] ar = args[i]; 279 280 if( ar == "--help" ) 281 { 282 Trace.formatln("Usage: {} <mediafilename>", args[0]); 283 return -1; 284 } 285 } 286 } 287 */ 288 289 if( mediaFileUri != "" ) 290 playMediaFile(mediaFileUri); 291 292 } 293 294 void playMediaFile(char[] file) 295 { 296 if( source !is null ) 297 { 298 fullStop(); 299 delete source; 300 } 301 if( videosink !is null ) 302 delete videosink; 303 304 // create elements 305 306 source = ElementFactory.make("playbin", "ourplaybin"); 307 //source = ElementFactory.make("playbin2", "ourplaybin");//playbin2 doesn't work, 308 //correctly with XOverlay. 309 videosink = ElementFactory.make("xvimagesink", "video-output-xvimagesink"); 310 //Only xvimagesink work (almost) correctly with XOverlay, but even it still 311 //has some problems. It won't work with compositing enabled... 312 //videosink = ElementFactory.make("autovideosink", "video-output-sink"); 313 //videosink = ElementFactory.make("fakesink", "video-sink"); 314 315 if( source is null ) 316 { 317 Trace.formatln("PlayBin could not be created"); 318 throw new Exception("One or more gstreamerD elements could not be created."); 319 } 320 321 if( videosink is null ) 322 { 323 Trace.formatln("videosink could not be created"); 324 throw new Exception("One or more gstreamerD elements could not be created."); 325 } 326 327 //add message handlers 328 source.getBus().setSyncHandler( &createXOverlayWindowCb ); 329 source.getBus().addWatch( &busCall ); 330 331 //Some Value handling, to get our videosink C GstElement* 332 //to be accepted by setProperty. This could propably made cleaner. 333 //One idea is to add a Element.setProperty(char[], void*); method. 334 335 Value val = new Value(); 336 //val.init(GType.POINTER); 337 //val.setPointer( cast(void*)(videosink.getElementStruct()) ); 338 val.init(GType.OBJECT); 339 //val.setObject( cast(GstElement*)videosink.getElementStruct() ); 340 val.setObject( videosink.getElementStruct() ); 341 source.setProperty( "video-sink", val ); 342 343 source.setProperty("uri", file); 344 play(); 345 } 346 347 348 ~this() 349 { 350 fullStop(); 351 } 352 353 void onPlay(Button button) 354 { 355 play(); 356 } 357 358 void play() 359 { 360 if( isPlaying == false ) 361 { 362 isPlaying = true; 363 // Now set to playing and iterate. 364 debug(1) Trace.formatln("Setting to PLAYING."); 365 //pipeline.setState( GstState.PLAYING ); 366 source.setState( GstState.PLAYING ); 367 debug(1) Trace.formatln("Running."); 368 } 369 else 370 { 371 isPlaying = false; 372 source.setState( GstState.PAUSED ); 373 } 374 } 375 376 void fullStop() 377 { 378 if( source !is null ) 379 source.setState( GstState.NULL ); 380 isPlaying = false; 381 } 382 383 void onSeekToStart(Button button) 384 { 385 //source.seek( 5 * GST_SECOND );//seek to 5 seconds. 386 source.seek( 0 );//seek to start. 387 } 388 389 void onOpen(Button button) 390 { 391 runImportMaterialFileChooser(); 392 } 393 394 void onQuit(Button button) 395 { 396 fullStop(); 397 Main.quit(); 398 } 399 400 void onAspectComboBoxChanged( ComboBox combo ) 401 { 402 char[] asp = combo.getActiveText(); 403 if( asp == "16:9" ) 404 monitorAspectFrame.set( 0.5, 0.5, 16.0/9.0, false ); 405 else //if( asp == "4:3" ) 406 monitorAspectFrame.set( 0.5, 0.5, 4.0/3.0, false ); 407 408 } 409 410 void runImportMaterialFileChooser() 411 { 412 char[][] a; 413 ResponseType[] r; 414 a ~= "Play file"; 415 a ~= "Close"; 416 r ~= ResponseType.APPLY;//OK; 417 r ~= ResponseType.CANCEL; 418 if ( importMaterialFileChooserDialog is null ) 419 { 420 importMaterialFileChooserDialog = new FileChooserDialog("Play mediafile", this, FileChooserAction.OPEN, a, r); 421 } 422 423 if( importMaterialFileChooserDialog.run() != ResponseType.CANCEL ) 424 { 425 //mediaFileUri = importMaterialFileChooserDialog.getFilename(); 426 mediaFileUri = importMaterialFileChooserDialog.getUri(); 427 428 Trace.formatln( "file selected: {}", mediaFileUri ); 429 playMediaFile(mediaFileUri); 430 } 431 432 importMaterialFileChooserDialog.hide(); 433 } 434 435 protected: 436 437 char[] mediaFileUri = ""; 438 439 Element source, videosink; 440 441 VBox vbox; 442 HBox buttonsHBox; 443 Button openButton; 444 Button playButton; 445 bool isPlaying(bool set) 446 { 447 m_isPlaying = set; 448 /* 449 //For some reason enabling this change of playButton 450 //label, from Play to Pause, will cause the XOverlay 451 //not to show the picture while we're on Pause. 452 //That's why it's disabled. XOverlay just doesn't work 453 //properly... 454 if( playButton !is null ) 455 { 456 if( m_isPlaying == true ) 457 { 458 playButton.setLabel("Pause"); 459 } 460 else 461 { 462 playButton.setLabel("Play"); 463 } 464 } 465 */ 466 return m_isPlaying; 467 } 468 bool isPlaying() { return m_isPlaying; } 469 bool m_isPlaying = false; 470 471 ComboBox aspectComboBox; 472 Button quitButton; 473 474 MonitorOverlay monitorOverlay; 475 AspectFrame monitorAspectFrame; 476 477 FileChooserDialog importMaterialFileChooserDialog; 478 } 479 480 481 int main(char[][] args) 482 { 483 Trace.formatln("gstreamerD GstMediaPlayer"); 484 485 uint major, minor, micro, nano; 486 487 Trace.formatln("Trying to init..."); 488 489 Main.init(args); 490 GStreamer.init(args); 491 492 Trace.formatln("Checking version of GStreamer..."); 493 GStreamer.versio(major, minor, micro, nano); 494 Trace.formatln("The installed version of GStreamer is {}.{}.{}", major, minor, micro ); 495 496 GstMediaPlayer gstMediaPlayer = new GstMediaPlayer(args); 497 498 //We must use the gtkD mainloop to run gstreamerD apps. 499 Main.run(); 500 501 return 0; 502 } 503 504