summaryrefslogtreecommitdiff
path: root/samples/BasicTutorial5.cs
blob: b82564dd20ad1f1d236121258ee8d63619ab4d29 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
// 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;
			IntPtr windowID = IntPtr.Zero;

			// 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:
				windowID = gdk_win32_drawable_get_handle (window.Handle);
				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.
			Playbin ["uri"] = "http://download.blender.org/durian/trailer/sintel_trailer-1080p.mp4";

			// 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") ]
		static extern IntPtr gdk_x11_window_get_xid (IntPtr handle);

		[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 ();
	}
}