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 
21 
22 #include <gst/pbutils/missing-plugins.h>
23 
24 #if defined(MEDIA_HUB_HAVE_HYBRIS_MEDIA_COMPAT_LAYER)
25 #include <hybris/media/surface_texture_client_hybris.h>
26 #include <hybris/media/media_codec_layer.h>
27 
30 
31 #include <utility>
32 
33 namespace
34 {
35 void setup_video_sink_for_buffer_streaming(GstElement* pipeline)
36 {
37  // Get the service-side BufferQueue (IGraphicBufferProducer) and associate it with
38  // the SurfaceTextureClientHybris instance
39  IGBPWrapperHybris igbp = decoding_service_get_igraphicbufferproducer();
40  SurfaceTextureClientHybris stc = surface_texture_client_create_by_igbp(igbp);
41 
42  // Because mirsink is being loaded, we are definitely doing * hardware rendering.
43  surface_texture_client_set_hardware_rendering(stc, TRUE);
44 
45  GstContext *context = gst_context_new("gst.mir.MirContext", TRUE);
46  GstStructure *structure = gst_context_writable_structure(context);
47  gst_structure_set(structure, "gst_mir_context", G_TYPE_POINTER, stc, NULL);
48 
49  /* Propagate context in pipeline (needed by amchybris and mirsink) */
50  gst_element_set_context(pipeline, context);
51 }
52 }
53 #else // MEDIA_HUB_HAVE_HYBRIS_MEDIA_COMPAT_LAYER
54 namespace
55 {
56 void setup_video_sink_for_buffer_streaming(GstElement*)
57 {
59 }
60 }
61 #endif // MEDIA_HUB_HAVE_HYBRIS_MEDIA_COMPAT_LAYER
62 
63 namespace
64 {
65 bool is_mir_video_sink()
66 {
67  return g_strcmp0(::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME"), "mirsink") == 0;
68 }
69 }
70 // Uncomment to generate a dot file at the time that the pipeline
71 // goes to the PLAYING state. Make sure to export GST_DEBUG_DUMP_DOT_DIR
72 // before starting media-hub-server. To convert the dot file to something
73 // other image format, use: dot pipeline.dot -Tpng -o pipeline.png
74 //#define DEBUG_GST_PIPELINE
75 
76 namespace media = core::ubuntu::media;
78 
80 {
81  static const std::string s{"playbin"};
82  return s;
83 }
84 
85 void gstreamer::Playbin::about_to_finish(GstElement*, gpointer user_data)
86 {
87  auto thiz = static_cast<Playbin*>(user_data);
88  thiz->signals.about_to_finish();
89 }
90 
92  GstElement *source,
93  gpointer user_data)
94 {
95  if (user_data == nullptr)
96  return;
97 
98  static_cast<Playbin*>(user_data)->setup_source(source);
99 }
100 
102  : pipeline(gst_element_factory_make("playbin", pipeline_name().c_str())),
103  bus{gst_element_get_bus(pipeline)},
105  video_sink(nullptr),
106  audio_sink(nullptr),
108  bus.on_new_message_async.connect(
109  std::bind(
111  this,
112  std::placeholders::_1))),
113  is_seeking(false),
118  player_lifetime(media::Player::Lifetime::normal),
121  is_missing_audio_codec(false),
122  is_missing_video_codec(false),
123  audio_stream_id(-1),
124  video_stream_id(-1),
125  current_new_state(GST_STATE_NULL)
126 {
127  if (!pipeline)
128  throw std::runtime_error("Could not create pipeline for playbin.");
129 
130  // Add audio and/or video sink elements depending on environment variables
131  // being set or not set
133 
134  about_to_finish_handler_id = g_signal_connect(
135  pipeline,
136  "about-to-finish",
137  G_CALLBACK(about_to_finish),
138  this
139  );
140 
141  source_setup_handler_id = g_signal_connect(
142  pipeline,
143  "source-setup",
144  G_CALLBACK(source_setup),
145  this
146  );
147 }
148 
149 // Note that we might be accessing freed memory here, so activate DEBUG_REFS
150 // only for debugging
151 //#define DEBUG_REFS
152 #ifdef DEBUG_REFS
153 static void print_refs(const gstreamer::Playbin &pb, const char *func)
154 {
155  using namespace std;
156 
157  MH_DEBUG("%s", func);
158  if (pb.pipeline)
159  MH_DEBUG("pipeline: %d", GST_OBJECT_REFCOUNT(pb.pipeline));
160  if (pb.video_sink)
161  MH_DEBUG("video_sink: %d", GST_OBJECT_REFCOUNT(pb.video_sink));
162  if (pb.audio_sink)
163  MH_DEBUG("audio_sink: %d", GST_OBJECT_REFCOUNT(pb.audio_sink));
164 }
165 #endif
166 
168 {
169 #ifdef DEBUG_REFS
170  print_refs(*this, "gstreamer::Playbin::~Playbin pipeline");
171 #endif
172 
173  g_signal_handler_disconnect(pipeline, about_to_finish_handler_id);
174  g_signal_handler_disconnect(pipeline, source_setup_handler_id);
175 
176  if (pipeline)
177  gst_object_unref(pipeline);
178 
179 #ifdef DEBUG_REFS
180  print_refs(*this, "gstreamer::Playbin::~Playbin pipeline");
181 #endif
182 }
183 
185 {
186  MH_INFO("Client died, resetting pipeline");
187  // When the client dies, tear down the current pipeline and get it
188  // in a state that is ready for the next client that connects to the
189  // service
190 
191  // Don't reset the pipeline if we want to resume
192  if (player_lifetime != media::Player::Lifetime::resumable) {
193  reset_pipeline();
194  }
195  // Signal to the Player class that the client side has disconnected
196  signals.client_disconnected();
197 }
198 
200 {
201  MH_TRACE("");
202  const auto ret = gst_element_set_state(pipeline, GST_STATE_NULL);
203  switch (ret)
204  {
205  case GST_STATE_CHANGE_FAILURE:
206  MH_WARNING("Failed to reset the pipeline state. Client reconnect may not function properly.");
207  break;
208  case GST_STATE_CHANGE_NO_PREROLL:
209  case GST_STATE_CHANGE_SUCCESS:
210  case GST_STATE_CHANGE_ASYNC:
211  break;
212  default:
213  MH_WARNING("Failed to reset the pipeline state. Client reconnect may not function properly.");
214  }
216  is_missing_audio_codec = false;
217  is_missing_video_codec = false;
218  audio_stream_id = -1;
219  video_stream_id = -1;
220 }
221 
223 {
224  if (!gst_is_missing_plugin_message(message))
225  return;
226 
227  gchar *desc = gst_missing_plugin_message_get_description(message);
228  MH_WARNING("Missing plugin: %s", desc);
229  g_free(desc);
230 
231  const GstStructure *msg_data = gst_message_get_structure(message);
232  if (g_strcmp0("decoder", gst_structure_get_string(msg_data, "type")) != 0)
233  return;
234 
235  GstCaps *caps;
236  if (!gst_structure_get(msg_data, "detail", GST_TYPE_CAPS, &caps, NULL)) {
237  MH_ERROR("No detail");
238  return;
239  }
240 
241  GstStructure *caps_data = gst_caps_get_structure(caps, 0);
242  if (!caps_data) {
243  MH_ERROR("No caps data");
244  return;
245  }
246 
247  const gchar *mime = gst_structure_get_name(caps_data);
248  if (strstr(mime, "audio"))
249  is_missing_audio_codec = true;
250  else if (strstr(mime, "video"))
251  is_missing_video_codec = true;
252 
253  MH_ERROR("Missing decoder for %s", mime);
254 }
255 
257 {
258  switch (message.type)
259  {
260  case GST_MESSAGE_ERROR:
261  signals.on_error(message.detail.error_warning_info);
262  break;
263  case GST_MESSAGE_WARNING:
264  signals.on_warning(message.detail.error_warning_info);
265  break;
266  case GST_MESSAGE_INFO:
267  signals.on_info(message.detail.error_warning_info);
268  break;
269  case GST_MESSAGE_STATE_CHANGED:
270  if (message.source == "playbin") {
271  g_object_get(G_OBJECT(pipeline), "current-audio", &audio_stream_id, NULL);
272  g_object_get(G_OBJECT(pipeline), "current-video", &video_stream_id, NULL);
273  }
274  signals.on_state_changed(std::make_pair(message.detail.state_changed, message.source));
275  break;
276  case GST_MESSAGE_ELEMENT:
278  break;
279  case GST_MESSAGE_TAG:
280  {
281  gchar *orientation;
282  if (gst_tag_list_get_string(message.detail.tag.tag_list, "image-orientation", &orientation))
283  {
284  // If the image-orientation tag is in the GstTagList, signal the Engine
285  signals.on_orientation_changed(orientation_lut(orientation));
286  g_free (orientation);
287  }
288 
289  signals.on_tag_available(message.detail.tag);
290  }
291  break;
292  case GST_MESSAGE_ASYNC_DONE:
293  if (is_seeking)
294  {
295  // FIXME: Pass the actual playback time position to the signal call
296  signals.on_seeked_to(0);
297  is_seeking = false;
298  }
299  break;
300  case GST_MESSAGE_EOS:
301  signals.on_end_of_stream();
302  break;
303  case GST_MESSAGE_BUFFERING:
304  signals.on_buffering_changed(message.detail.buffering.percent);
305  break;
306  default:
307  break;
308  }
309 }
310 
312 {
313  return bus;
314 }
315 
317 {
318  gint flags;
319  g_object_get (pipeline, "flags", &flags, nullptr);
320  flags |= GST_PLAY_FLAG_AUDIO;
321  flags |= GST_PLAY_FLAG_VIDEO;
322  flags &= ~GST_PLAY_FLAG_TEXT;
323  g_object_set (pipeline, "flags", flags, nullptr);
324 
325  if (::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME") != nullptr)
326  {
327  audio_sink = gst_element_factory_make (
328  ::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME"),
329  "audio-sink");
330 
331  MH_INFO("audio_sink: %s", ::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME"));
332 
333  g_object_set (
334  pipeline,
335  "audio-sink",
336  audio_sink,
337  NULL);
338  }
339 
340  if (::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME") != nullptr)
341  {
342  video_sink = gst_element_factory_make (
343  ::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME"),
344  "video-sink");
345 
346  MH_INFO("video_sink: %s", ::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME"));
347 
348  g_object_set (
349  pipeline,
350  "video-sink",
351  video_sink,
352  NULL);
353  }
354 }
355 
357 {
358  if (not video_sink) throw std::logic_error
359  {
360  "No video sink configured for the current pipeline"
361  };
362 
363  setup_video_sink_for_buffer_streaming(pipeline);
364 }
365 
366 void gstreamer::Playbin::set_volume(double new_volume)
367 {
368  g_object_set (pipeline, "volume", new_volume, NULL);
369 }
370 
373 {
374  switch (audio_role)
375  {
376  case media::Player::AudioStreamRole::alarm:
377  return "alarm";
378  break;
379  case media::Player::AudioStreamRole::alert:
380  return "alert";
381  break;
382  case media::Player::AudioStreamRole::multimedia:
383  return "multimedia";
384  break;
385  case media::Player::AudioStreamRole::phone:
386  return "phone";
387  break;
388  default:
389  return "multimedia";
390  break;
391  }
392 }
393 
395 {
396  if (g_strcmp0(orientation, "rotate-0") == 0)
397  return media::Player::Orientation::rotate0;
398  else if (g_strcmp0(orientation, "rotate-90") == 0)
399  return media::Player::Orientation::rotate90;
400  else if (g_strcmp0(orientation, "rotate-180") == 0)
401  return media::Player::Orientation::rotate180;
402  else if (g_strcmp0(orientation, "rotate-270") == 0)
403  return media::Player::Orientation::rotate270;
404  else
405  return media::Player::Orientation::rotate0;
406 }
407 
410 {
411  const std::string role_str("props,media.role=" + get_audio_role_str(new_audio_role));
412  MH_INFO("Audio stream role: %s", role_str);
413 
414  GstStructure *props = gst_structure_from_string (role_str.c_str(), NULL);
415  if (audio_sink != nullptr && props != nullptr)
416  {
417  g_object_set (audio_sink, "stream-properties", props, NULL);
418  }
419  else
420  {
421  MH_WARNING("Couldn't set audio stream role - couldn't get audio_sink from pipeline");
422  }
423 
424  gst_structure_free (props);
425 }
426 
428 {
429  player_lifetime = lifetime;
430 }
431 
433 {
434  int64_t pos = 0;
435  gst_element_query_position (pipeline, GST_FORMAT_TIME, &pos);
436 
437  // This prevents a 0 position from being reported to the app which happens while seeking.
438  // This is covering over a GStreamer issue
439  if ((static_cast<uint64_t>(pos) < duration()) && is_seeking && pos == 0)
440  {
441  return previous_position;
442  }
443 
444  // Save the current position to use just in case it's needed the next time position is
445  // requested
446  previous_position = static_cast<uint64_t>(pos);
447 
448  // FIXME: this should be int64_t, but dbus-cpp doesn't seem to handle it correctly
449  return static_cast<uint64_t>(pos);
450 }
451 
453 {
454  int64_t dur = 0;
455  gst_element_query_duration (pipeline, GST_FORMAT_TIME, &dur);
456 
457  // FIXME: this should be int64_t, but dbus-cpp doesn't seem to handle it correctly
458  return static_cast<uint64_t>(dur);
459 }
460 
462  const std::string& uri,
464  bool do_pipeline_reset)
465 {
466  gchar *current_uri = nullptr;
467  g_object_get(pipeline, "current-uri", &current_uri, NULL);
468 
469  // Checking for a current_uri being set and not resetting the pipeline
470  // if there isn't a current_uri causes the first play to start playback
471  // sooner since reset_pipeline won't be called
472  if (current_uri and do_pipeline_reset)
473  reset_pipeline();
474 
475  std::string tmp_uri{uri};
476  media::UriCheck::Ptr uri_check{std::make_shared<media::UriCheck>(uri)};
477  if (uri_check->is_local_file())
478  {
479  if (uri_check->is_encoded())
480  {
481  // First decode the URI just in case it's partially encoded already
482  tmp_uri = decode_uri(uri);
483  MH_DEBUG("File URI was encoded, now decoded: %s", tmp_uri);
484  }
485  tmp_uri = encode_uri(tmp_uri);
486  }
487 
488  g_object_set(pipeline, "uri", tmp_uri.c_str(), NULL);
489  if (is_video_file(tmp_uri))
491  else if (is_audio_file(tmp_uri))
493 
494  request_headers = headers;
495 
496  g_free(current_uri);
497 }
498 
499 void gstreamer::Playbin::setup_source(GstElement *source)
500 {
501  if (source == NULL || request_headers.empty())
502  return;
503 
504  if (request_headers.find("Cookie") != request_headers.end()) {
505  if (g_object_class_find_property(G_OBJECT_GET_CLASS(source),
506  "cookies") != NULL) {
507  gchar ** cookies = g_strsplit(request_headers["Cookie"].c_str(), ";", 0);
508  g_object_set(source, "cookies", cookies, NULL);
509  g_strfreev(cookies);
510  }
511  }
512 
513  if (request_headers.find("User-Agent") != request_headers.end()) {
514  if (g_object_class_find_property(G_OBJECT_GET_CLASS(source),
515  "user-agent") != NULL) {
516  g_object_set(source, "user-agent", request_headers["User-Agent"].c_str(), NULL);
517  }
518  }
519 }
520 
521 std::string gstreamer::Playbin::uri() const
522 {
523  gchar* data = nullptr;
524  g_object_get(pipeline, "current-uri", &data, nullptr);
525 
526  std::string result((data == nullptr ? "" : data));
527  g_free(data);
528 
529  return result;
530 }
531 
533 {
534  MH_TRACE("");
535  auto thiz = static_cast<Playbin*>(user_data);
536  if (thiz and thiz->pipeline)
537  gst_element_set_state(thiz->pipeline, thiz->current_new_state);
538 
539  // Always return false so this is a single shot function call
540  return false;
541 }
542 bool gstreamer::Playbin::set_state_and_wait(GstState new_state, bool use_main_thread)
543 {
544  static const std::chrono::nanoseconds state_change_timeout
545  {
546  // We choose a quite high value here as tests are run under valgrind
547  // and gstreamer pipeline setup/state changes take longer in that scenario.
548  // The value does not negatively impact runtime performance.
549  std::chrono::milliseconds{5000}
550  };
551 
552  bool result = false;
553  GstState current, pending;
554  if (use_main_thread)
555  {
556  // Cache this value for the static g_idle_add handler function
557  current_new_state = new_state;
558  g_idle_add((GSourceFunc) gstreamer::Playbin::set_state_in_main_thread, (gpointer) this);
559 
560  MH_DEBUG("Requested state change in main thread context.");
561 
562  GstState current, pending;
563  result = GST_STATE_CHANGE_SUCCESS == gst_element_get_state(
564  pipeline,
565  &current,
566  &pending,
567  state_change_timeout.count());
568  }
569  else
570  {
571  const auto ret = gst_element_set_state(pipeline, new_state);
572 
573  MH_DEBUG("Requested state change not using main thread context.");
574 
575  switch (ret)
576  {
577  case GST_STATE_CHANGE_FAILURE:
578  result = false; break;
579  case GST_STATE_CHANGE_NO_PREROLL:
580  case GST_STATE_CHANGE_SUCCESS:
581  result = true; break;
582  case GST_STATE_CHANGE_ASYNC:
583  result = GST_STATE_CHANGE_SUCCESS == gst_element_get_state(
584  pipeline,
585  &current,
586  &pending,
587  state_change_timeout.count());
588  break;
589  }
590  }
591 
592  // We only should query the pipeline if we actually succeeded in
593  // setting the requested state.
594  if (result && new_state == GST_STATE_PLAYING)
595  {
596  // Get the video height/width from the video sink
597  try
598  {
601  cached_video_dimensions = new_dimensions;
602  }
603  catch (const std::exception& e)
604  {
605  MH_WARNING("Problem querying video dimensions: %s", e.what());
606  }
607  catch (...)
608  {
609  MH_WARNING("Problem querying video dimensions.");
610  }
611 
612 #ifdef DEBUG_GST_PIPELINE
613  MH_DEBUG("Dumping pipeline dot file");
614  GST_DEBUG_BIN_TO_DOT_FILE((GstBin*)pipeline, GST_DEBUG_GRAPH_SHOW_ALL, "pipeline");
615 #endif
616  }
617 
618  return result;
619 }
620 
621 bool gstreamer::Playbin::seek(const std::chrono::microseconds& ms)
622 {
623  is_seeking = true;
624  return gst_element_seek_simple(
625  pipeline,
626  GST_FORMAT_TIME,
627  (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT),
628  ms.count() * 1000);
629 }
630 
632 {
633  if (not video_sink || not is_mir_video_sink())
634  throw std::runtime_error
635  {
636  "Missing video sink or video sink does not support query of width and height."
637  };
638 
639  // Initialize to default value prior to querying actual values from the sink.
640  uint32_t video_width = 0, video_height = 0;
641  g_object_get (video_sink, "height", &video_height, nullptr);
642  g_object_get (video_sink, "width", &video_width, nullptr);
643 
644  // TODO(tvoss): We should probably check here if width and height are valid.
646  {
649  };
650 }
651 
653 {
654  // Only signal the application layer if the dimensions have in fact changed. This might happen
655  // if reusing the same media-hub session to play two different video sources.
656  if (new_dimensions != cached_video_dimensions)
657  signals.on_video_dimensions_changed(new_dimensions);
658 }
659 
660 std::string gstreamer::Playbin::file_info_from_uri(const std::string& uri) const
661 {
662  GError *error = nullptr;
663  // Open the URI and get the mime type from it. This will currently only work for
664  // a local file
665  std::unique_ptr<GFile, void(*)(void *)> file(
666  g_file_new_for_uri(uri.c_str()), g_object_unref);
667  std::unique_ptr<GFileInfo, void(*)(void *)> info(
668  g_file_query_info(
669  file.get(), G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE ","
670  G_FILE_ATTRIBUTE_ETAG_VALUE, G_FILE_QUERY_INFO_NONE,
671  /* cancellable */ NULL, &error),
672  g_object_unref);
673  if (!info)
674  return std::string();
675 
676  std::string content_type = g_file_info_get_attribute_string(
677  info.get(), G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE);
678  if (content_type.empty())
679  return std::string();
680 
681  if (content_type == "application/octet-stream")
682  {
683  std::unique_ptr<GFileInfo, void(*)(void *)> full_info(
684  g_file_query_info(file.get(), G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
685  G_FILE_QUERY_INFO_NONE,
686  /* cancellable */ NULL, &error),g_object_unref);
687 
688  if (!full_info)
689  return std::string();
690 
691  content_type = g_file_info_get_attribute_string(
692  full_info.get(), G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
693  if (content_type.empty())
694  return std::string();
695  }
696  return content_type;
697 }
698 
699 std::string gstreamer::Playbin::encode_uri(const std::string& uri) const
700 {
701  if (uri.empty())
702  return std::string();
703 
704  std::string encoded_uri;
705  media::UriCheck::Ptr uri_check{std::make_shared<media::UriCheck>(uri)};
706  gchar *uri_scheme = g_uri_parse_scheme(uri.c_str());
707  // We have a URI and it is already percent encoded
708  if (uri_scheme and strlen(uri_scheme) > 0 and uri_check->is_encoded())
709  {
710  MH_DEBUG("Is a URI and is already percent encoded");
711  encoded_uri = uri;
712  }
713  // We have a URI but it's not already percent encoded
714  else if (uri_scheme and strlen(uri_scheme) > 0 and !uri_check->is_encoded())
715  {
716  MH_DEBUG("Is a URI and is not already percent encoded");
717  gchar *encoded = g_uri_escape_string(uri.c_str(),
718  "!$&'()*+,;=:/?[]@", // reserved chars
719  TRUE); // Allow UTF-8 chars
720  if (!encoded)
721  {
722  g_free(uri_scheme);
723  return std::string();
724  }
725  encoded_uri.assign(encoded);
726  g_free(encoded);
727  }
728  else // We have a path and not a URI. Turn it into a full URI and encode it
729  {
730  GError *error = nullptr;
731  MH_DEBUG("Is a path and is not already percent encoded");
732  gchar *str = g_filename_to_uri(uri.c_str(), nullptr, &error);
733  if (!str)
734  {
735  g_free(uri_scheme);
736  return std::string();
737  }
738  encoded_uri.assign(str);
739  g_free(str);
740  if (error != nullptr)
741  {
742  MH_WARNING("Failed to get actual track content type: %s", error->message);
743  g_error_free(error);
744  g_free(str);
745  g_free(uri_scheme);
746  return std::string("audio/video/");
747  }
748  gchar *escaped = g_uri_escape_string(encoded_uri.c_str(),
749  "!$&'()*+,;=:/?[]@", // reserved chars
750  TRUE); // Allow UTF-8 chars
751  if (!escaped)
752  {
753  g_free(uri_scheme);
754  return std::string();
755  }
756  encoded_uri.assign(escaped);
757  g_free(escaped);
758  }
759 
760  g_free(uri_scheme);
761 
762  return encoded_uri;
763 }
764 
765 std::string gstreamer::Playbin::decode_uri(const std::string& uri) const
766 {
767  if (uri.empty())
768  return std::string();
769 
770  gchar *decoded_gchar = g_uri_unescape_string(uri.c_str(), nullptr);
771  if (!decoded_gchar)
772  return std::string();
773 
774  const std::string decoded{decoded_gchar};
775  g_free(decoded_gchar);
776  return decoded;
777 }
778 
779 std::string gstreamer::Playbin::get_file_content_type(const std::string& uri) const
780 {
781  if (uri.empty())
782  return std::string();
783 
784  const std::string encoded_uri{encode_uri(uri)};
785 
786  const std::string content_type {file_info_from_uri(encoded_uri)};
787  if (content_type.empty())
788  {
789  MH_WARNING("Failed to get actual track content type");
790  return std::string("audio/video/");
791  }
792 
793  MH_INFO("Found content type: %s", content_type);
794 
795  return content_type;
796 }
797 
798 bool gstreamer::Playbin::is_audio_file(const std::string& uri) const
799 {
800  if (uri.empty())
801  return false;
802 
803  if (get_file_content_type(uri).find("audio/") == 0)
804  {
805  MH_INFO("Found audio content");
806  return true;
807  }
808 
809  return false;
810 }
811 
812 bool gstreamer::Playbin::is_video_file(const std::string& uri) const
813 {
814  if (uri.empty())
815  return false;
816 
817  if (get_file_content_type(uri).find("video/") == 0)
818  {
819  MH_INFO("Found video content");
820  return true;
821  }
822 
823  return false;
824 }
825 
827 {
828  return file_type;
829 }
830 
832 {
833  /*
834  * We do not consider that we can play the video when
835  * 1. No audio stream selected due to missing decoder
836  * 2. No video stream selected due to missing decoder
837  * 3. No stream selected at all
838  * Note that if there are several, say, audio streams, we will play the file
839  * provided that we can decode just one of them, even if there are missing
840  * audio codecs. We will also play files with only one type of stream.
841  */
842  if ((is_missing_audio_codec && audio_stream_id == -1) ||
844  (audio_stream_id == -1 && video_stream_id == -1))
845  return false;
846  else
847  return true;
848 }
static std::string get_audio_role_str(core::ubuntu::media::Player::AudioStreamRole audio_role)
Definition: playbin.cpp:372
static void source_setup(GstElement *, GstElement *source, gpointer user_data)
Definition: playbin.cpp:91
void setup_source(GstElement *source)
Definition: playbin.cpp:499
std::tuple< Height, Width > Dimensions
Height and Width of a video.
Definition: dimensions.h:139
gint audio_stream_id
Definition: playbin.h:155
void set_uri(const std::string &uri, const core::ubuntu::media::Player::HeadersType &headers, bool do_pipeline_reset=true)
Definition: playbin.cpp:461
core::ubuntu::media::video::Dimensions get_video_dimensions() const
Definition: playbin.cpp:631
GstMessageType type
Definition: bus.h:181
void process_message_element(GstMessage *message)
Definition: playbin.cpp:222
#define MH_INFO(...)
Definition: logger.h:125
static gboolean set_state_in_main_thread(gpointer user_data)
Definition: playbin.cpp:532
bool seek(const std::chrono::microseconds &ms)
Definition: playbin.cpp:621
uint64_t duration() const
Definition: playbin.cpp:452
void reset_pipeline()
Definition: playbin.cpp:199
core::Connection on_new_message_connection_async
Definition: playbin.h:129
static const std::string & pipeline_name()
Definition: playbin.cpp:79
void emit_video_dimensions_changed_if_changed(const core::ubuntu::media::video::Dimensions &new_dimensions)
Definition: playbin.cpp:652
STL namespace.
bool can_play_streams() const
Definition: playbin.cpp:831
void set_lifetime(core::ubuntu::media::Player::Lifetime)
Definition: playbin.cpp:427
#define MH_ERROR(...)
Definition: logger.h:128
struct gstreamer::Playbin::@12 signals
GstElement * video_sink
Definition: playbin.h:127
void set_audio_stream_role(core::ubuntu::media::Player::AudioStreamRole new_audio_role)
Definition: playbin.cpp:409
bool is_missing_audio_codec
Definition: playbin.h:153
gint video_stream_id
Definition: playbin.h:156
std::map< std::string, std::string > HeadersType
Definition: player.h:64
GstState current_new_state
Definition: playbin.h:157
#define MH_DEBUG(...)
Definition: logger.h:123
core::ubuntu::media::Player::Orientation orientation_lut(const gchar *orientation)
Definition: playbin.cpp:394
#define MH_WARNING(...)
Definition: logger.h:127
struct gstreamer::Bus::Message::Detail::@1 buffering
gstreamer::Bus & message_bus()
Definition: playbin.cpp:311
core::Signal< void > about_to_finish
Definition: playbin.h:139
std::shared_ptr< UriCheck > Ptr
Definition: uri_check.h:37
std::string uri() const
Definition: playbin.cpp:521
void setup_pipeline_for_audio_video()
Definition: playbin.cpp:316
bool set_state_and_wait(GstState new_state, bool use_main_thread=false)
Definition: playbin.cpp:542
GstMessage * message
Definition: bus.h:180
union gstreamer::Bus::Message::Detail detail
GstElement * pipeline
Definition: playbin.h:124
GstElement * audio_sink
Definition: playbin.h:128
std::string get_file_content_type(const std::string &uri) const
Definition: playbin.cpp:779
std::string encode_uri(const std::string &uri) const
Definition: playbin.cpp:699
gulong source_setup_handler_id
Definition: playbin.h:136
#define MH_TRACE(...)
Definition: logger.h:121
core::ubuntu::media::video::Dimensions cached_video_dimensions
Definition: playbin.h:132
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:798
bool is_video_file(const std::string &uri) const
Definition: playbin.cpp:812
struct gstreamer::Bus::Message::Detail::ErrorWarningInfo error_warning_info
void set_volume(double new_volume)
Definition: playbin.cpp:366
void on_new_message_async(const Bus::Message &message)
Definition: playbin.cpp:256
void create_video_sink(uint32_t texture_id)
Definition: playbin.cpp:356
gulong about_to_finish_handler_id
Definition: playbin.h:135
gstreamer::Bus bus
Definition: playbin.h:125
bool is_missing_video_codec
Definition: playbin.h:154
core::Signal< Message > on_new_message_async
Definition: bus.h:333
MediaFileType file_type
Definition: playbin.h:126
core::ubuntu::media::Player::HeadersType request_headers
Definition: playbin.h:133
uint64_t position() const
Definition: playbin.cpp:432
IntWrapper is a type-safe integer that allows for encoding/enforcing semantics by means of tags...
Definition: dimensions.h:68
std::string decode_uri(const std::string &uri) const
Definition: playbin.cpp:765
MediaFileType media_file_type() const
Definition: playbin.cpp:826
struct gstreamer::Bus::Message::Detail::StateChanged state_changed
uint64_t previous_position
Definition: playbin.h:131
core::ubuntu::media::Player::Lifetime player_lifetime
Definition: playbin.h:134
std::string file_info_from_uri(const std::string &uri) const
Definition: playbin.cpp:660