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 (Message.NewApplication (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 ();
}
}
|