1 /* 2 * gst_mediaplayer is placed in the 3 * public domain. 4 */ 5 6 module gst_mediaplayer; 7 8 import std.conv; 9 import std.file; 10 import std.path; 11 import std.stdio; 12 13 //gtkD imports: 14 import cairo.Context; 15 16 import gtk.Main; 17 import gtk.MainWindow; 18 19 import gtk.Widget; 20 import gdk.Window; 21 import gdk.Event; 22 import gtk.DrawingArea; 23 import gtk.AspectFrame; 24 25 import gtk.FileChooserDialog; 26 import gtk.FileFilter; 27 28 import gdk.X11;//Needed for VideoOverlay 29 30 import gtk.VBox; 31 import gtk.HBox; 32 import gtk.Button; 33 import gtk.ComboBoxText; 34 35 import gobject.Value; 36 37 //gstreamerD imports: 38 39 import gstreamer.GStreamer; 40 41 import gobject.ObjectG; 42 import glib.ErrorG; 43 import gstreamer.Element; 44 import gstreamer.Bin; 45 import gstreamer.Pipeline; 46 import gstreamer.ElementFactory; 47 import gstreamer.Pad; 48 import gstreamer.Message; 49 import gstreamer.Structure; 50 import gstreamer.Bus; 51 52 import gstinterfaces.VideoOverlay; 53 54 55 class MonitorOverlay : DrawingArea 56 { 57 public: 58 59 this() 60 { 61 debug(MonitorOverlay) writeln("Monitor.this() START."); 62 debug(MonitorOverlay) scope(exit) writeln("Monitor.this() END."); 63 64 setDoubleBuffered(false); 65 } 66 67 public VideoOverlay videoOverlay() { return m_videoOverlay; } 68 /** 69 * Give this method an VideoOverlay that you've created from 70 * a videoSink. like: monitorOverlay.videoOverlay = new VideoOverlay( videoSink ); 71 */ 72 public VideoOverlay videoOverlay(VideoOverlay set) 73 { 74 debug(MonitorOverlay) writeln("Monitor.videoOverlay(set) START."); 75 debug(MonitorOverlay) scope(exit) writeln("Monitor.videoOverlay(set) END."); 76 m_videoOverlay = set; 77 78 debug(MonitorOverlay) writeln("Monitor.videoOverlay(set) videoOverlay set. Now setting XwindowId."); 79 m_videoOverlay.setWindowHandle( getWindow().getXid() ); 80 81 debug(MonitorOverlay) writeln("X11.drawableGetXid: {}", getWindow().getXid() ); 82 83 return m_videoOverlay; 84 } 85 protected VideoOverlay m_videoOverlay; 86 } 87 88 89 class GstMediaPlayer : MainWindow 90 { 91 public: 92 93 GstBusSyncReply createVideoOverlayWindowCb( Message msg ) 94 { 95 // ignore anything but 'prepare-window-handle' element messages 96 if( msg.type() != GstMessageType.ELEMENT ) 97 return GstBusSyncReply.PASS; 98 99 Structure str = msg.getStructure(); 100 101 if( str.hasName("prepare-window-handle") == false ) 102 return GstBusSyncReply.PASS; 103 104 debug(MonitorOverlay) writeln("Now we should create the X window."); 105 106 monitorOverlay.videoOverlay = new VideoOverlay( videosink ); 107 108 debug(MonitorOverlay) writeln("Created an VideoOverlay."); 109 110 return GstBusSyncReply.DROP; 111 } 112 113 bool busCall( Message msg ) 114 { 115 debug(gstreamer) writeln("GstMediaPlayer.busCall(msg) START."); 116 debug(gstreamer) scope(exit) writeln("GstMediaPlayer.busCall(msg) END."); 117 118 switch( msg.type ) 119 { 120 case GstMessageType.UNKNOWN: 121 writeln("Unknown message type."); 122 break; 123 124 case GstMessageType.EOS: 125 writeln("End-of-stream. Looping from the start."); 126 onSeekToStart(null); 127 break; 128 129 case GstMessageType.ERROR: 130 { 131 string dbug; 132 ErrorG err; 133 msg.parseError(err, dbug); 134 writefln("Error: %s dbug: %s", to!string(err.getErrorGStruct().message), dbug ); 135 //g_error_free (err); 136 Main.quit(); 137 break; 138 } 139 default: 140 break; 141 } 142 143 return true; 144 } 145 146 this(string[] args) 147 { 148 super("GstMediaPlayer"); 149 150 setSizeRequest(600, 400); 151 152 vbox = new VBox(false,0); 153 154 monitorOverlay = new MonitorOverlay(); 155 monitorAspectFrame = new AspectFrame("", 0.5, 0.5, 16.0/9.0, false ); 156 157 monitorAspectFrame.add( monitorOverlay ); 158 monitorAspectFrame.setShadowType( GtkShadowType.NONE ); 159 monitorAspectFrame.setLabelWidget( null );//Yes! This get's rid of that stupid label on top of the aspectframe! More room for the monitor. 160 161 vbox.packStart( monitorAspectFrame, true, true, 0 ); 162 163 buttonsHBox = new HBox(false,0); 164 165 openButton = new Button("Open..."); 166 openButton.addOnClicked( &onOpen ); 167 buttonsHBox.packStart( openButton, false, false, 0 ); 168 169 playButton = new Button("Play"); 170 playButton.addOnClicked( &onPlay ); 171 buttonsHBox.packStart( playButton, false, false, 0 ); 172 173 aspectComboBox = new ComboBoxText();//Create a new text ComboBox. 174 aspectComboBox.appendText("16:9"); 175 aspectComboBox.appendText("4:3"); 176 aspectComboBox.setActiveText("16:9"); 177 aspectComboBox.addOnChanged( &onAspectComboBoxChanged ); 178 179 buttonsHBox.packStart( aspectComboBox, false, false, 0 ); 180 181 quitButton = new Button(StockID.QUIT); 182 quitButton.addOnClicked( &onQuit ); 183 buttonsHBox.packStart( quitButton, false, false, 0 ); 184 185 vbox.packStart( buttonsHBox, false, false, 0 ); 186 187 add( vbox ); 188 189 showAll(); 190 191 if (args.length > 1) 192 { 193 mediaFileUri = args[1]; 194 195 if ( !isRooted(mediaFileUri) ) 196 { 197 mediaFileUri = buildNormalizedPath(getcwd(), mediaFileUri); 198 } 199 200 //This will construct the filename to be a URI. 201 if( mediaFileUri[0..7] != "file://" && mediaFileUri[0..7] != "http://" ) 202 mediaFileUri = "file://"~ mediaFileUri; 203 } 204 205 if( mediaFileUri != "" ) 206 playMediaFile(mediaFileUri); 207 208 } 209 210 void playMediaFile(string file) 211 { 212 if( source !is null ) 213 { 214 fullStop(); 215 delete source; 216 } 217 if( videosink !is null ) 218 delete videosink; 219 220 // create elements 221 222 source = ElementFactory.make("playbin", "ourplaybin"); 223 videosink = ElementFactory.make("ximagesink", "video-output-xvimagesink"); 224 //Only xvimagesink work (almost) correctly with VideoOverlay, but even it still 225 //has some problems. It won't work with compositing enabled... 226 227 if( source is null ) 228 { 229 writeln("PlayBin could not be created"); 230 throw new Exception("One or more gstreamerD elements could not be created."); 231 } 232 233 if( videosink is null ) 234 { 235 writeln("videosink could not be created"); 236 throw new Exception("One or more gstreamerD elements could not be created."); 237 } 238 239 //add message handlers 240 source.getBus().setSyncHandler( &createVideoOverlayWindowCb ); 241 source.getBus().addWatch( &busCall ); 242 243 //Some Value handling, to get our videosink C GstElement* 244 //to be accepted by setProperty. This could propably made cleaner. 245 //One idea is to add a Element.setProperty(string, void*); method. 246 247 Value val = new Value(); 248 val.init(GType.OBJECT); 249 val.setObject( videosink ); 250 source.setProperty( "video-sink", val ); 251 252 source.setProperty("uri", file); 253 play(); 254 } 255 256 257 ~this() 258 { 259 fullStop(); 260 } 261 262 void onPlay(Button button) 263 { 264 play(); 265 } 266 267 void play() 268 { 269 if( isPlaying == false ) 270 { 271 isPlaying = true; 272 // Now set to playing and iterate. 273 debug(1) writeln("Setting to PLAYING."); 274 //pipeline.setState( GstState.PLAYING ); 275 source.setState( GstState.PLAYING ); 276 debug(1) writeln("Running."); 277 } 278 else 279 { 280 isPlaying = false; 281 source.setState( GstState.PAUSED ); 282 } 283 } 284 285 void fullStop() 286 { 287 if( source !is null ) 288 source.setState( GstState.NULL ); 289 isPlaying = false; 290 } 291 292 void onSeekToStart(Button button) 293 { 294 source.seek( 0 );//seek to start. 295 } 296 297 void onOpen(Button button) 298 { 299 runImportMaterialFileChooser(); 300 } 301 302 void onQuit(Button button) 303 { 304 fullStop(); 305 Main.quit(); 306 } 307 308 void onAspectComboBoxChanged( ComboBoxText combo ) 309 { 310 string asp = combo.getActiveText(); 311 if( asp == "16:9" ) 312 monitorAspectFrame.set( 0.5, 0.5, 16.0/9.0, false ); 313 else //if( asp == "4:3" ) 314 monitorAspectFrame.set( 0.5, 0.5, 4.0/3.0, false ); 315 316 } 317 318 void runImportMaterialFileChooser() 319 { 320 string[] a = ["Play file", "Close"]; 321 ResponseType[] r = [ResponseType.APPLY, ResponseType.CANCEL]; 322 323 if ( importMaterialFileChooserDialog is null ) 324 { 325 importMaterialFileChooserDialog = new FileChooserDialog("Play mediafile", this, FileChooserAction.OPEN, a, r); 326 327 FileFilter all = new FileFilter(); 328 all.setName("All files"); 329 all.addPattern("*"); 330 importMaterialFileChooserDialog.addFilter(all); 331 332 FileFilter files = new FileFilter(); 333 files.setName("Supported files"); 334 foreach( mime; supportedMimeTypes ) 335 files.addMimeType(mime); 336 importMaterialFileChooserDialog.addFilter(files); 337 importMaterialFileChooserDialog.setFilter(files); 338 } 339 340 if( importMaterialFileChooserDialog.run() != ResponseType.CANCEL ) 341 { 342 mediaFileUri = importMaterialFileChooserDialog.getUri(); 343 344 writefln( "file selected: %s", mediaFileUri ); 345 playMediaFile(mediaFileUri); 346 } 347 348 importMaterialFileChooserDialog.hide(); 349 } 350 351 protected: 352 353 string mediaFileUri = ""; 354 355 Element source, videosink; 356 357 VBox vbox; 358 HBox buttonsHBox; 359 Button openButton; 360 Button playButton; 361 bool isPlaying(bool set) 362 { 363 m_isPlaying = set; 364 365 if( playButton !is null ) 366 { 367 if( m_isPlaying == true ) 368 { 369 playButton.setLabel("Pause"); 370 } 371 else 372 { 373 playButton.setLabel("Play"); 374 } 375 } 376 377 return m_isPlaying; 378 } 379 bool isPlaying() { return m_isPlaying; } 380 bool m_isPlaying = false; 381 382 ComboBoxText aspectComboBox; 383 Button quitButton; 384 385 MonitorOverlay monitorOverlay; 386 AspectFrame monitorAspectFrame; 387 388 FileChooserDialog importMaterialFileChooserDialog; 389 390 string[] supportedMimeTypes = 391 ["application/ogg", "application/ram", "application/sdp", "application/smil", 392 "application/smil+xml", "application/vnd.rn-realmedia", "application/x-extension-m4a", 393 "application/x-extension-mp4", "application/x-flac", "application/x-flash-video", 394 "application/x-matroska", "application/x-netshow-channel", "application/x-ogg", 395 "application/x-quicktime-media-link", "application/x-quicktimeplayer", "application/x-shorten", 396 "application/x-smil", "audio/3gpp", "audio/ac3", "audio/AMR", "audio/AMR-WB", "audio/basic", 397 "audio/midi", "audio/mp4", "audio/mpeg", "audio/ogg", "audio/vnd.rn-realaudio", "audio/x-ape", 398 "audio/x-flac", "audio/x-it", "audio/x-m4a", "audio/x-matroska", "audio/x-mod", "audio/x-mp3", 399 "audio/x-mpeg", "audio/x-musepack", "audio/x-pn-aiff", "audio/x-pn-au", "audio/x-pn-realaudio", 400 "audio/x-pn-realaudio-plugin", "audio/x-pn-wav", "audio/x-pn-windows-acm", "audio/x-realaudio", 401 "audio/x-real-audio", "audio/x-sbc", "audio/x-speex", "audio/x-tta", "audio/x-wav", "audio/x-wavpack", 402 "audio/x-vorbis", "audio/x-vorbis+ogg", "audio/x-xm", "image/vnd.rn-realpix", "image/x-pict", 403 "misc/ultravox", "text/google-video-pointer", "text/x-google-video-pointer", "video/3gpp", 404 "video/dv", "video/fli", "video/flv", "video/mp4", "video/mp4v-es", "video/mpeg", "video/msvideo", 405 "video/ogg", "video/quicktime", "video/vivo", "video/vnd.divx", "video/vnd.rn-realvideo", 406 "video/vnd.vivo", "video/x-anim", "video/x-avi", "video/x-flc", "video/x-fli", "video/x-flic", 407 "video/x-flv", "video/x-m4v", "video/x-matroska", "video/x-mpeg", "video/x-ms-asf", "video/x-msvideo", 408 "video/x-ms-wm", "video/x-ms-wmv", "video/x-nsv", "video/x-ogm+ogg", "video/x-theora+ogg", 409 "x-content/video-dvd", "x-content/video-vcd", "x-content/video-svcd"]; 410 } 411 412 void main(string[] args) 413 { 414 writeln("gstreamerD GstMediaPlayer"); 415 416 uint major, minor, micro, nano; 417 418 writeln("Trying to init..."); 419 420 Main.init(args); 421 GStreamer.init(args); 422 423 writeln("Checking version of GStreamer..."); 424 GStreamer.versio(major, minor, micro, nano); 425 writefln("The installed version of GStreamer is %s.%s.%s", major, minor, micro ); 426 427 GstMediaPlayer gstMediaPlayer = new GstMediaPlayer(args); 428 429 //We must use the gtkD mainloop to run gstreamerD apps. 430 Main.run(); 431 }