23 #include <gst/pbutils/missing-plugins.h>
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>
33 void setup_video_sink_for_buffer_streaming(GstElement* pipeline)
37 IGBPWrapperHybris igbp = decoding_service_get_igraphicbufferproducer();
38 SurfaceTextureClientHybris stc = surface_texture_client_create_by_igbp(igbp);
41 surface_texture_client_set_hardware_rendering(stc, TRUE);
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);
48 gst_element_set_context(pipeline, context);
51 #else // MEDIA_HUB_HAVE_HYBRIS_MEDIA_COMPAT_LAYER
54 void setup_video_sink_for_buffer_streaming(GstElement*)
59 #endif // MEDIA_HUB_HAVE_HYBRIS_MEDIA_COMPAT_LAYER
63 bool is_mir_video_sink()
65 return g_strcmp0(::getenv(
"CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME"),
"mirsink") == 0;
79 static const std::string s{
"playbin"};
85 auto thiz =
static_cast<Playbin*
>(user_data);
93 if (user_data ==
nullptr)
96 static_cast<Playbin*
>(user_data)->setup_source(source);
100 : pipeline(gst_element_factory_make(
"playbin", pipeline_name().c_str())),
102 file_type(MEDIA_FILE_TYPE_NONE),
104 on_new_message_connection_async(
105 bus.on_new_message_async.connect(
109 std::placeholders::_1))),
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),
124 throw std::runtime_error(
"Could not create pipeline for playbin.");
128 setup_pipeline_for_audio_video();
130 about_to_finish_handler_id = g_signal_connect(
133 G_CALLBACK(about_to_finish),
137 source_setup_handler_id = g_signal_connect(
140 G_CALLBACK(source_setup),
147 g_signal_handler_disconnect(pipeline, about_to_finish_handler_id);
148 g_signal_handler_disconnect(pipeline, source_setup_handler_id);
151 gst_object_unref(pipeline);
156 std::cout <<
"Client died, resetting pipeline" << std::endl;
162 if (player_lifetime != media::Player::Lifetime::resumable) {
166 signals.client_disconnected();
171 std::cout << __PRETTY_FUNCTION__ << std::endl;
172 auto ret = gst_element_set_state(pipeline, GST_STATE_NULL);
175 case GST_STATE_CHANGE_FAILURE:
176 std::cout <<
"Failed to reset the pipeline state. Client reconnect may not function properly." << std::endl;
178 case GST_STATE_CHANGE_NO_PREROLL:
179 case GST_STATE_CHANGE_SUCCESS:
180 case GST_STATE_CHANGE_ASYNC:
183 std::cout <<
"Failed to reset the pipeline state. Client reconnect may not function properly." << std::endl;
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;
194 if (!gst_is_missing_plugin_message(message))
197 gchar *desc = gst_missing_plugin_message_get_description(message);
198 std::cerr <<
"Missing plugin: " << desc << std::endl;
201 const GstStructure *msg_data = gst_message_get_structure(message);
202 if (g_strcmp0(
"decoder", gst_structure_get_string(msg_data,
"type")) != 0)
206 if (!gst_structure_get(msg_data,
"detail", GST_TYPE_CAPS, &caps, NULL)) {
207 std::cerr << __PRETTY_FUNCTION__ <<
": No detail" << std::endl;
211 GstStructure *caps_data = gst_caps_get_structure(caps, 0);
213 std::cerr << __PRETTY_FUNCTION__ <<
": No caps data" << std::endl;
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;
223 std::cerr <<
"Missing decoder for " << mime << std::endl;
230 case GST_MESSAGE_ERROR:
233 case GST_MESSAGE_WARNING:
236 case GST_MESSAGE_INFO:
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);
246 case GST_MESSAGE_ELEMENT:
247 process_message_element(message.
message);
249 case GST_MESSAGE_TAG:
252 if (gst_tag_list_get_string(message.
detail.
tag.
tag_list,
"image-orientation", &orientation))
255 signals.on_orientation_changed(orientation_lut(orientation));
256 g_free (orientation);
259 signals.on_tag_available(message.
detail.
tag);
262 case GST_MESSAGE_ASYNC_DONE:
266 signals.on_seeked_to(0);
270 case GST_MESSAGE_EOS:
271 signals.on_end_of_stream();
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);
291 if (::getenv(
"CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME") !=
nullptr)
293 auto audio_sink = gst_element_factory_make (
294 ::getenv(
"CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME"),
297 std::cout <<
"audio_sink: " << ::getenv(
"CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME") << std::endl;
306 if (::getenv(
"CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME") !=
nullptr)
308 video_sink = gst_element_factory_make (
309 ::getenv(
"CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME"),
312 std::cout <<
"video_sink: " << ::getenv(
"CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME") << std::endl;
324 if (not video_sink)
throw std::logic_error
326 "No video sink configured for the current pipeline"
329 setup_video_sink_for_buffer_streaming(pipeline);
334 g_object_set (pipeline,
"volume", new_volume, NULL);
342 case media::Player::AudioStreamRole::alarm:
345 case media::Player::AudioStreamRole::alert:
348 case media::Player::AudioStreamRole::multimedia:
351 case media::Player::AudioStreamRole::phone:
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;
371 return media::Player::Orientation::rotate0;
377 GstElement *audio_sink = NULL;
378 g_object_get (pipeline,
"audio-sink", &audio_sink, NULL);
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;
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);
389 "Warning: couldn't set audio stream role - couldn't get audio_sink from pipeline" <<
393 gst_structure_free (props);
398 player_lifetime = lifetime;
404 gst_element_query_position (pipeline, GST_FORMAT_TIME, &pos);
408 if ((static_cast<uint64_t>(pos) < duration()) && is_seeking && pos == 0)
410 return previous_position;
415 previous_position =
static_cast<uint64_t
>(pos);
418 return static_cast<uint64_t
>(pos);
424 gst_element_query_duration (pipeline, GST_FORMAT_TIME, &dur);
427 return static_cast<uint64_t
>(dur);
431 const std::string& uri,
433 bool do_pipeline_reset)
435 if (do_pipeline_reset)
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;
444 request_headers = headers;
449 if (source == NULL || request_headers.empty())
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);
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);
471 gchar* data =
nullptr;
472 g_object_get(pipeline,
"current-uri", &data,
nullptr);
474 std::string result((data ==
nullptr ?
"" : data));
482 static const std::chrono::nanoseconds state_change_timeout
487 std::chrono::milliseconds{5000}
490 auto ret = gst_element_set_state(pipeline, new_state);
492 std::cout << __PRETTY_FUNCTION__ <<
": requested state change." << std::endl;
494 bool result =
false; GstState current, pending;
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(
507 state_change_timeout.count());
513 if (result && new_state == GST_STATE_PLAYING)
519 emit_video_dimensions_changed_if_changed(new_dimensions);
520 cached_video_dimensions = new_dimensions;
522 catch (
const std::exception& e)
524 std::cerr <<
"Problem querying video dimensions: " << e.what() << std::endl;
528 std::cerr <<
"Problem querying video dimensions." << std::endl;
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");
543 return gst_element_seek_simple(
546 (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT),
552 if (not video_sink || not is_mir_video_sink())
553 throw std::runtime_error
555 "Missing video sink or video sink does not support query of width and height."
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);
575 if (new_dimensions != cached_video_dimensions)
576 signals.on_video_dimensions_changed(new_dimensions);
582 return std::string();
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);
592 return std::string(
"audio/video/");
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(
600 file.get(), G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE
","
601 G_FILE_ATTRIBUTE_ETAG_VALUE, G_FILE_QUERY_INFO_NONE,
606 std::string error_str(error->message);
609 std::cout <<
"Failed to query the URI for the presence of video content: "
610 << error_str << std::endl;
611 return std::string();
614 std::string content_type(g_file_info_get_attribute_string(
615 info.get(), G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE));
625 if (get_file_content_type(uri).find(
"audio/") == 0)
627 std::cout <<
"Found audio content" << std::endl;
639 if (get_file_content_type(uri).find(
"video/") == 0)
641 std::cout <<
"Found video content" << std::endl;
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))
static std::string get_audio_role_str(core::ubuntu::media::Player::AudioStreamRole audio_role)
bool set_state_and_wait(GstState new_state)
static void source_setup(GstElement *, GstElement *source, gpointer user_data)
void setup_source(GstElement *source)
void set_uri(const std::string &uri, const core::ubuntu::media::Player::HeadersType &headers, bool do_pipeline_reset=true)
core::ubuntu::media::video::Dimensions get_video_dimensions() const
void process_message_element(GstMessage *message)
bool seek(const std::chrono::microseconds &ms)
uint64_t duration() const
static const std::string & pipeline_name()
void emit_video_dimensions_changed_if_changed(const core::ubuntu::media::video::Dimensions &new_dimensions)
bool can_play_streams() const
void set_lifetime(core::ubuntu::media::Player::Lifetime)
struct gstreamer::Playbin::@12 signals
void set_audio_stream_role(core::ubuntu::media::Player::AudioStreamRole new_audio_role)
core::ubuntu::media::Player::Orientation orientation_lut(const gchar *orientation)
gstreamer::Bus & message_bus()
core::Signal< void > about_to_finish
void setup_pipeline_for_audio_video()
union gstreamer::Bus::Message::Detail detail
std::string get_file_content_type(const std::string &uri) const
struct gstreamer::Bus::Message::Detail::Tag tag
boost::flyweight< std::string > source
bool is_audio_file(const std::string &uri) const
bool is_video_file(const std::string &uri) const
struct gstreamer::Bus::Message::Detail::ErrorWarningInfo error_warning_info
void set_volume(double new_volume)
void on_new_message_async(const Bus::Message &message)
void create_video_sink(uint32_t texture_id)
uint64_t position() const
MediaFileType media_file_type() const
struct gstreamer::Bus::Message::Detail::StateChanged state_changed