Music Hub  ..
A session-wide music playback service
playbin.cpp
Go to the documentation of this file.
1 /*
2  * Copyright © 2013-2015 Canonical Ltd.
3  *
4  * This program is free software: you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License version 3,
6  * as published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  *
16  * Authored by: Thomas Voß <thomas.voss@canonical.com>
17  */
18 
20 
22 
23 #include <gst/pbutils/missing-plugins.h>
24 
25 #if defined(MEDIA_HUB_HAVE_HYBRIS_MEDIA_COMPAT_LAYER)
26 #include <hybris/media/surface_texture_client_hybris.h>
27 #include <hybris/media/media_codec_layer.h>
28 
29 #include <utility>
30 
31 namespace
32 {
33 void setup_video_sink_for_buffer_streaming(GstElement* pipeline)
34 {
35  // Get the service-side BufferQueue (IGraphicBufferProducer) and associate it with
36  // the SurfaceTextureClientHybris instance
37  IGBPWrapperHybris igbp = decoding_service_get_igraphicbufferproducer();
38  SurfaceTextureClientHybris stc = surface_texture_client_create_by_igbp(igbp);
39 
40  // Because mirsink is being loaded, we are definitely doing * hardware rendering.
41  surface_texture_client_set_hardware_rendering(stc, TRUE);
42 
43  GstContext *context = gst_context_new("gst.mir.MirContext", TRUE);
44  GstStructure *structure = gst_context_writable_structure(context);
45  gst_structure_set(structure, "gst_mir_context", G_TYPE_POINTER, stc, NULL);
46 
47  /* Propagate context in pipeline (needed by amchybris and mirsink) */
48  gst_element_set_context(pipeline, context);
49 }
50 }
51 #else // MEDIA_HUB_HAVE_HYBRIS_MEDIA_COMPAT_LAYER
52 namespace
53 {
54 void setup_video_sink_for_buffer_streaming(GstElement*)
55 {
57 }
58 }
59 #endif // MEDIA_HUB_HAVE_HYBRIS_MEDIA_COMPAT_LAYER
60 
61 namespace
62 {
63 bool is_mir_video_sink()
64 {
65  return g_strcmp0(::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME"), "mirsink") == 0;
66 }
67 }
68 // Uncomment to generate a dot file at the time that the pipeline
69 // goes to the PLAYING state. Make sure to export GST_DEBUG_DUMP_DOT_DIR
70 // before starting media-hub-server. To convert the dot file to something
71 // other image format, use: dot pipeline.dot -Tpng -o pipeline.png
72 //#define DEBUG_GST_PIPELINE
73 
74 namespace media = core::ubuntu::media;
76 
78 {
79  static const std::string s{"playbin"};
80  return s;
81 }
82 
83 void gstreamer::Playbin::about_to_finish(GstElement*, gpointer user_data)
84 {
85  auto thiz = static_cast<Playbin*>(user_data);
86  thiz->signals.about_to_finish();
87 }
88 
90  GstElement *source,
91  gpointer user_data)
92 {
93  if (user_data == nullptr)
94  return;
95 
96  static_cast<Playbin*>(user_data)->setup_source(source);
97 }
98 
100  : pipeline(gst_element_factory_make("playbin", pipeline_name().c_str())),
101  bus{gst_element_get_bus(pipeline)},
102  file_type(MEDIA_FILE_TYPE_NONE),
103  video_sink(nullptr),
104  on_new_message_connection_async(
105  bus.on_new_message_async.connect(
106  std::bind(
108  this,
109  std::placeholders::_1))),
110  is_seeking(false),
111  previous_position(0),
112  cached_video_dimensions{
115  player_lifetime(media::Player::Lifetime::normal),
116  about_to_finish_handler_id(0),
117  source_setup_handler_id(0),
118  is_missing_audio_codec(false),
119  is_missing_video_codec(false),
120  audio_stream_id(-1),
121  video_stream_id(-1)
122 {
123  if (!pipeline)
124  throw std::runtime_error("Could not create pipeline for playbin.");
125 
126  // Add audio and/or video sink elements depending on environment variables
127  // being set or not set
128  setup_pipeline_for_audio_video();
129 
130  about_to_finish_handler_id = g_signal_connect(
131  pipeline,
132  "about-to-finish",
133  G_CALLBACK(about_to_finish),
134  this
135  );
136 
137  source_setup_handler_id = g_signal_connect(
138  pipeline,
139  "source-setup",
140  G_CALLBACK(source_setup),
141  this
142  );
143 }
144 
146 {
147  g_signal_handler_disconnect(pipeline, about_to_finish_handler_id);
148  g_signal_handler_disconnect(pipeline, source_setup_handler_id);
149 
150  if (pipeline)
151  gst_object_unref(pipeline);
152 }
153 
155 {
156  std::cout << "Client died, resetting pipeline" << std::endl;
157  // When the client dies, tear down the current pipeline and get it
158  // in a state that is ready for the next client that connects to the
159  // service
160 
161  // Don't reset the pipeline if we want to resume
162  if (player_lifetime != media::Player::Lifetime::resumable) {
163  reset_pipeline();
164  }
165  // Signal to the Player class that the client side has disconnected
166  signals.client_disconnected();
167 }
168 
170 {
171  std::cout << __PRETTY_FUNCTION__ << std::endl;
172  auto ret = gst_element_set_state(pipeline, GST_STATE_NULL);
173  switch(ret)
174  {
175  case GST_STATE_CHANGE_FAILURE:
176  std::cout << "Failed to reset the pipeline state. Client reconnect may not function properly." << std::endl;
177  break;
178  case GST_STATE_CHANGE_NO_PREROLL:
179  case GST_STATE_CHANGE_SUCCESS:
180  case GST_STATE_CHANGE_ASYNC:
181  break;
182  default:
183  std::cout << "Failed to reset the pipeline state. Client reconnect may not function properly." << std::endl;
184  }
185  file_type = MEDIA_FILE_TYPE_NONE;
186  is_missing_audio_codec = false;
187  is_missing_video_codec = false;
188  audio_stream_id = -1;
189  video_stream_id = -1;
190 }
191 
193 {
194  if (!gst_is_missing_plugin_message(message))
195  return;
196 
197  gchar *desc = gst_missing_plugin_message_get_description(message);
198  std::cerr << "Missing plugin: " << desc << std::endl;
199  g_free(desc);
200 
201  const GstStructure *msg_data = gst_message_get_structure(message);
202  if (g_strcmp0("decoder", gst_structure_get_string(msg_data, "type")) != 0)
203  return;
204 
205  GstCaps *caps;
206  if (!gst_structure_get(msg_data, "detail", GST_TYPE_CAPS, &caps, NULL)) {
207  std::cerr << __PRETTY_FUNCTION__ << ": No detail" << std::endl;
208  return;
209  }
210 
211  GstStructure *caps_data = gst_caps_get_structure(caps, 0);
212  if (!caps_data) {
213  std::cerr << __PRETTY_FUNCTION__ << ": No caps data" << std::endl;
214  return;
215  }
216 
217  const gchar *mime = gst_structure_get_name(caps_data);
218  if (strstr(mime, "audio"))
219  is_missing_audio_codec = true;
220  else if (strstr(mime, "video"))
221  is_missing_video_codec = true;
222 
223  std::cerr << "Missing decoder for " << mime << std::endl;
224 }
225 
227 {
228  switch(message.type)
229  {
230  case GST_MESSAGE_ERROR:
231  signals.on_error(message.detail.error_warning_info);
232  break;
233  case GST_MESSAGE_WARNING:
234  signals.on_warning(message.detail.error_warning_info);
235  break;
236  case GST_MESSAGE_INFO:
237  signals.on_info(message.detail.error_warning_info);
238  break;
239  case GST_MESSAGE_STATE_CHANGED:
240  if (message.source == "playbin") {
241  g_object_get(G_OBJECT(pipeline), "current-audio", &audio_stream_id, NULL);
242  g_object_get(G_OBJECT(pipeline), "current-video", &video_stream_id, NULL);
243  }
244  signals.on_state_changed(std::make_pair(message.detail.state_changed, message.source));
245  break;
246  case GST_MESSAGE_ELEMENT:
247  process_message_element(message.message);
248  break;
249  case GST_MESSAGE_TAG:
250  {
251  gchar *orientation;
252  if (gst_tag_list_get_string(message.detail.tag.tag_list, "image-orientation", &orientation))
253  {
254  // If the image-orientation tag is in the GstTagList, signal the Engine
255  signals.on_orientation_changed(orientation_lut(orientation));
256  g_free (orientation);
257  }
258 
259  signals.on_tag_available(message.detail.tag);
260  }
261  break;
262  case GST_MESSAGE_ASYNC_DONE:
263  if (is_seeking)
264  {
265  // FIXME: Pass the actual playback time position to the signal call
266  signals.on_seeked_to(0);
267  is_seeking = false;
268  }
269  break;
270  case GST_MESSAGE_EOS:
271  signals.on_end_of_stream();
272  default:
273  break;
274  }
275 }
276 
278 {
279  return bus;
280 }
281 
283 {
284  gint flags;
285  g_object_get (pipeline, "flags", &flags, nullptr);
286  flags |= GST_PLAY_FLAG_AUDIO;
287  flags |= GST_PLAY_FLAG_VIDEO;
288  flags &= ~GST_PLAY_FLAG_TEXT;
289  g_object_set (pipeline, "flags", flags, nullptr);
290 
291  if (::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME") != nullptr)
292  {
293  auto audio_sink = gst_element_factory_make (
294  ::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME"),
295  "audio-sink");
296 
297  std::cout << "audio_sink: " << ::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME") << std::endl;
298 
299  g_object_set (
300  pipeline,
301  "audio-sink",
302  audio_sink,
303  NULL);
304  }
305 
306  if (::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME") != nullptr)
307  {
308  video_sink = gst_element_factory_make (
309  ::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME"),
310  "video-sink");
311 
312  std::cout << "video_sink: " << ::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME") << std::endl;
313 
314  g_object_set (
315  pipeline,
316  "video-sink",
317  video_sink,
318  NULL);
319  }
320 }
321 
323 {
324  if (not video_sink) throw std::logic_error
325  {
326  "No video sink configured for the current pipeline"
327  };
328 
329  setup_video_sink_for_buffer_streaming(pipeline);
330 }
331 
332 void gstreamer::Playbin::set_volume(double new_volume)
333 {
334  g_object_set (pipeline, "volume", new_volume, NULL);
335 }
336 
339 {
340  switch (audio_role)
341  {
342  case media::Player::AudioStreamRole::alarm:
343  return "alarm";
344  break;
345  case media::Player::AudioStreamRole::alert:
346  return "alert";
347  break;
348  case media::Player::AudioStreamRole::multimedia:
349  return "multimedia";
350  break;
351  case media::Player::AudioStreamRole::phone:
352  return "phone";
353  break;
354  default:
355  return "multimedia";
356  break;
357  }
358 }
359 
361 {
362  if (g_strcmp0(orientation, "rotate-0") == 0)
363  return media::Player::Orientation::rotate0;
364  else if (g_strcmp0(orientation, "rotate-90") == 0)
365  return media::Player::Orientation::rotate90;
366  else if (g_strcmp0(orientation, "rotate-180") == 0)
367  return media::Player::Orientation::rotate180;
368  else if (g_strcmp0(orientation, "rotate-270") == 0)
369  return media::Player::Orientation::rotate270;
370  else
371  return media::Player::Orientation::rotate0;
372 }
373 
376 {
377  GstElement *audio_sink = NULL;
378  g_object_get (pipeline, "audio-sink", &audio_sink, NULL);
379 
380  std::string role_str("props,media.role=" + get_audio_role_str(new_audio_role));
381  std::cout << "Audio stream role: " << role_str << std::endl;
382 
383  GstStructure *props = gst_structure_from_string (role_str.c_str(), NULL);
384  if (audio_sink != nullptr && props != nullptr)
385  g_object_set (audio_sink, "stream-properties", props, NULL);
386  else
387  {
388  std::cerr <<
389  "Warning: couldn't set audio stream role - couldn't get audio_sink from pipeline" <<
390  std::endl;
391  }
392 
393  gst_structure_free (props);
394 }
395 
397 {
398  player_lifetime = lifetime;
399 }
400 
402 {
403  int64_t pos = 0;
404  gst_element_query_position (pipeline, GST_FORMAT_TIME, &pos);
405 
406  // This prevents a 0 position from being reported to the app which happens while seeking.
407  // This is covering over a GStreamer issue
408  if ((static_cast<uint64_t>(pos) < duration()) && is_seeking && pos == 0)
409  {
410  return previous_position;
411  }
412 
413  // Save the current position to use just in case it's needed the next time position is
414  // requested
415  previous_position = static_cast<uint64_t>(pos);
416 
417  // FIXME: this should be int64_t, but dbus-cpp doesn't seem to handle it correctly
418  return static_cast<uint64_t>(pos);
419 }
420 
422 {
423  int64_t dur = 0;
424  gst_element_query_duration (pipeline, GST_FORMAT_TIME, &dur);
425 
426  // FIXME: this should be int64_t, but dbus-cpp doesn't seem to handle it correctly
427  return static_cast<uint64_t>(dur);
428 }
429 
431  const std::string& uri,
433  bool do_pipeline_reset)
434 {
435  if (do_pipeline_reset)
436  reset_pipeline();
437 
438  g_object_set(pipeline, "uri", uri.c_str(), NULL);
439  if (is_video_file(uri))
440  file_type = MEDIA_FILE_TYPE_VIDEO;
441  else if (is_audio_file(uri))
442  file_type = MEDIA_FILE_TYPE_AUDIO;
443 
444  request_headers = headers;
445 }
446 
447 void gstreamer::Playbin::setup_source(GstElement *source)
448 {
449  if (source == NULL || request_headers.empty())
450  return;
451 
452  if (request_headers.find("Cookie") != request_headers.end()) {
453  if (g_object_class_find_property(G_OBJECT_GET_CLASS(source),
454  "cookies") != NULL) {
455  gchar ** cookies = g_strsplit(request_headers["Cookie"].c_str(), ";", 0);
456  g_object_set(source, "cookies", cookies, NULL);
457  g_strfreev(cookies);
458  }
459  }
460 
461  if (request_headers.find("User-Agent") != request_headers.end()) {
462  if (g_object_class_find_property(G_OBJECT_GET_CLASS(source),
463  "user-agent") != NULL) {
464  g_object_set(source, "user-agent", request_headers["User-Agent"].c_str(), NULL);
465  }
466  }
467 }
468 
469 std::string gstreamer::Playbin::uri() const
470 {
471  gchar* data = nullptr;
472  g_object_get(pipeline, "current-uri", &data, nullptr);
473 
474  std::string result((data == nullptr ? "" : data));
475  g_free(data);
476 
477  return result;
478 }
479 
481 {
482  static const std::chrono::nanoseconds state_change_timeout
483  {
484  // We choose a quite high value here as tests are run under valgrind
485  // and gstreamer pipeline setup/state changes take longer in that scenario.
486  // The value does not negatively impact runtime performance.
487  std::chrono::milliseconds{5000}
488  };
489 
490  auto ret = gst_element_set_state(pipeline, new_state);
491 
492  std::cout << __PRETTY_FUNCTION__ << ": requested state change." << std::endl;
493 
494  bool result = false; GstState current, pending;
495  switch(ret)
496  {
497  case GST_STATE_CHANGE_FAILURE:
498  result = false; break;
499  case GST_STATE_CHANGE_NO_PREROLL:
500  case GST_STATE_CHANGE_SUCCESS:
501  result = true; break;
502  case GST_STATE_CHANGE_ASYNC:
503  result = GST_STATE_CHANGE_SUCCESS == gst_element_get_state(
504  pipeline,
505  &current,
506  &pending,
507  state_change_timeout.count());
508  break;
509  }
510 
511  // We only should query the pipeline if we actually succeeded in
512  // setting the requested state.
513  if (result && new_state == GST_STATE_PLAYING)
514  {
515  // Get the video height/width from the video sink
516  try
517  {
518  const core::ubuntu::media::video::Dimensions new_dimensions = get_video_dimensions();
519  emit_video_dimensions_changed_if_changed(new_dimensions);
520  cached_video_dimensions = new_dimensions;
521  }
522  catch (const std::exception& e)
523  {
524  std::cerr << "Problem querying video dimensions: " << e.what() << std::endl;
525  }
526  catch (...)
527  {
528  std::cerr << "Problem querying video dimensions." << std::endl;
529  }
530 
531 #ifdef DEBUG_GST_PIPELINE
532  std::cout << "Dumping pipeline dot file" << std::endl;
533  GST_DEBUG_BIN_TO_DOT_FILE((GstBin*)pipeline, GST_DEBUG_GRAPH_SHOW_ALL, "pipeline");
534 #endif
535  }
536 
537  return result;
538 }
539 
540 bool gstreamer::Playbin::seek(const std::chrono::microseconds& ms)
541 {
542  is_seeking = true;
543  return gst_element_seek_simple(
544  pipeline,
545  GST_FORMAT_TIME,
546  (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT),
547  ms.count() * 1000);
548 }
549 
551 {
552  if (not video_sink || not is_mir_video_sink())
553  throw std::runtime_error
554  {
555  "Missing video sink or video sink does not support query of width and height."
556  };
557 
558  // Initialize to default value prior to querying actual values from the sink.
559  uint32_t video_width = 0, video_height = 0;
560  g_object_get (video_sink, "height", &video_height, nullptr);
561  g_object_get (video_sink, "width", &video_width, nullptr);
562 
563  // TODO(tvoss): We should probably check here if width and height are valid.
565  {
568  };
569 }
570 
572 {
573  // Only signal the application layer if the dimensions have in fact changed. This might happen
574  // if reusing the same media-hub session to play two different video sources.
575  if (new_dimensions != cached_video_dimensions)
576  signals.on_video_dimensions_changed(new_dimensions);
577 }
578 
579 std::string gstreamer::Playbin::get_file_content_type(const std::string& uri) const
580 {
581  if (uri.empty())
582  return std::string();
583 
584  std::string filename(uri);
585  size_t pos = uri.find("file://");
586  if (pos != std::string::npos)
587  filename = uri.substr(pos + 7, std::string::npos);
588  else
589  // Anything other than a file, for now claim that the type
590  // is both audio and video.
591  // FIXME: implement true net stream sampling and get the type from GstCaps
592  return std::string("audio/video/");
593 
594 
595  GError *error = nullptr;
596  std::unique_ptr<GFile, void(*)(void *)> file(
597  g_file_new_for_path(filename.c_str()), g_object_unref);
598  std::unique_ptr<GFileInfo, void(*)(void *)> info(
599  g_file_query_info(
600  file.get(), G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE ","
601  G_FILE_ATTRIBUTE_ETAG_VALUE, G_FILE_QUERY_INFO_NONE,
602  /* cancellable */ NULL, &error),
603  g_object_unref);
604  if (!info)
605  {
606  std::string error_str(error->message);
607  g_error_free(error);
608 
609  std::cout << "Failed to query the URI for the presence of video content: "
610  << error_str << std::endl;
611  return std::string();
612  }
613 
614  std::string content_type(g_file_info_get_attribute_string(
615  info.get(), G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE));
616 
617  return content_type;
618 }
619 
620 bool gstreamer::Playbin::is_audio_file(const std::string& uri) const
621 {
622  if (uri.empty())
623  return false;
624 
625  if (get_file_content_type(uri).find("audio/") == 0)
626  {
627  std::cout << "Found audio content" << std::endl;
628  return true;
629  }
630 
631  return false;
632 }
633 
634 bool gstreamer::Playbin::is_video_file(const std::string& uri) const
635 {
636  if (uri.empty())
637  return false;
638 
639  if (get_file_content_type(uri).find("video/") == 0)
640  {
641  std::cout << "Found video content" << std::endl;
642  return true;
643  }
644 
645  return false;
646 }
647 
649 {
650  return file_type;
651 }
652 
654 {
655  /*
656  * We do not consider that we can play the video when
657  * 1. No audio stream selected due to missing decoder
658  * 2. No video stream selected due to missing decoder
659  * 3. No stream selected at all
660  * Note that if there are several, say, audio streams, we will play the file
661  * provided that we can decode just one of them, even if there are missing
662  * audio codecs. We will also play files with only one type of stream.
663  */
664  if ((is_missing_audio_codec && audio_stream_id == -1) ||
665  (is_missing_video_codec && video_stream_id == -1) ||
666  (audio_stream_id == -1 && video_stream_id == -1))
667  return false;
668  else
669  return true;
670 }
static std::string get_audio_role_str(core::ubuntu::media::Player::AudioStreamRole audio_role)
Definition: playbin.cpp:338
bool set_state_and_wait(GstState new_state)
Definition: playbin.cpp:480
static void source_setup(GstElement *, GstElement *source, gpointer user_data)
Definition: playbin.cpp:89
void setup_source(GstElement *source)
Definition: playbin.cpp:447
std::tuple< Height, Width > Dimensions
Height and Width of a video.
Definition: dimensions.h:139
void set_uri(const std::string &uri, const core::ubuntu::media::Player::HeadersType &headers, bool do_pipeline_reset=true)
Definition: playbin.cpp:430
core::ubuntu::media::video::Dimensions get_video_dimensions() const
Definition: playbin.cpp:550
GstMessageType type
Definition: bus.h:181
void process_message_element(GstMessage *message)
Definition: playbin.cpp:192
bool seek(const std::chrono::microseconds &ms)
Definition: playbin.cpp:540
uint64_t duration() const
Definition: playbin.cpp:421
void reset_pipeline()
Definition: playbin.cpp:169
static const std::string & pipeline_name()
Definition: playbin.cpp:77
void emit_video_dimensions_changed_if_changed(const core::ubuntu::media::video::Dimensions &new_dimensions)
Definition: playbin.cpp:571
bool can_play_streams() const
Definition: playbin.cpp:653
void set_lifetime(core::ubuntu::media::Player::Lifetime)
Definition: playbin.cpp:396
struct gstreamer::Playbin::@12 signals
void set_audio_stream_role(core::ubuntu::media::Player::AudioStreamRole new_audio_role)
Definition: playbin.cpp:375
std::map< std::string, std::string > HeadersType
Definition: player.h:49
core::ubuntu::media::Player::Orientation orientation_lut(const gchar *orientation)
Definition: playbin.cpp:360
gstreamer::Bus & message_bus()
Definition: playbin.cpp:277
core::Signal< void > about_to_finish
Definition: playbin.h:130
std::string uri() const
Definition: playbin.cpp:469
void setup_pipeline_for_audio_video()
Definition: playbin.cpp:282
GstMessage * message
Definition: bus.h:180
union gstreamer::Bus::Message::Detail detail
GstElement * pipeline
Definition: playbin.h:116
std::string get_file_content_type(const std::string &uri) const
Definition: playbin.cpp:579
struct gstreamer::Bus::Message::Detail::Tag tag
boost::flyweight< std::string > source
Definition: bus.h:182
bool is_audio_file(const std::string &uri) const
Definition: playbin.cpp:620
bool is_video_file(const std::string &uri) const
Definition: playbin.cpp:634
struct gstreamer::Bus::Message::Detail::ErrorWarningInfo error_warning_info
void set_volume(double new_volume)
Definition: playbin.cpp:332
void on_new_message_async(const Bus::Message &message)
Definition: playbin.cpp:226
void create_video_sink(uint32_t texture_id)
Definition: playbin.cpp:322
uint64_t position() const
Definition: playbin.cpp:401
IntWrapper is a type-safe integer that allows for encoding/enforcing semantics by means of tags...
Definition: dimensions.h:68
MediaFileType media_file_type() const
Definition: playbin.cpp:648
struct gstreamer::Bus::Message::Detail::StateChanged state_changed