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 			source.destroy();
216 		}
217 		if( videosink !is null )
218 			videosink.destroy();
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.version_(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 }