#include #include #include #include #include #include #include gboolean sigint(gpointer ptr) { g_main_loop_quit(static_cast < GMainLoop* > (ptr)); return TRUE; } class video_carousel { public: typedef std::vector < std::string > uris; explicit video_carousel(uris const &p_uris, guint const p_interval, std::string const &p_video_sink, std::string const &p_audio_sink, GMainLoop *p_loop) : m_pipeline(nullptr) , m_uris(p_uris) , m_interval(p_interval) , m_loop(p_loop) , m_timeout_source(nullptr) { GError *error; g_assert(!(m_uris.empty())); m_pipeline = gst_element_factory_make("playbin", "pipeline"); g_object_set(G_OBJECT(m_pipeline), "flags", gint(0x57), nullptr); if (!(p_video_sink.empty())) { GstElement *sink = gst_parse_bin_from_description(p_video_sink.c_str(), TRUE, &error); if (sink == nullptr) { gst_object_unref(GST_OBJECT(m_pipeline)); std::string str = std::string("Could not create video sink element: ") + error->message; g_clear_error(&error); throw std::runtime_error(str); } g_object_set(G_OBJECT(m_pipeline), "video-sink", sink, nullptr); } if (!(p_audio_sink.empty())) { GstElement *sink = gst_element_factory_make(p_audio_sink.c_str(), "audio-sink"); if (sink == nullptr) { gst_object_unref(GST_OBJECT(m_pipeline)); std::string str = std::string("Could not create audio sink element: ") + error->message; g_clear_error(&error); throw std::runtime_error(str); } g_object_set(G_OBJECT(m_pipeline), "audio-sink", sink, nullptr); } GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(m_pipeline)); gst_bus_add_watch(bus, static_bus_watch, this); gst_object_unref(GST_OBJECT(bus)); } void start() { gst_element_set_state(m_pipeline, GST_STATE_NULL); m_cur_uri = m_uris.begin(); g_object_set(G_OBJECT(m_pipeline), "uri", m_cur_uri->c_str(), nullptr); gst_element_set_state(m_pipeline, GST_STATE_PLAYING); } gint64 get_current_position() const { gint64 position; gboolean success = gst_element_query_position(GST_ELEMENT(m_pipeline), GST_FORMAT_TIME, &position); return success ? position : -1; } ~video_carousel() { if (m_pipeline != nullptr) { stop_timeout(); gst_element_set_state(m_pipeline, GST_STATE_NULL); gst_object_unref(GST_OBJECT(m_pipeline)); } } private: void start_timeout() { if ((m_timeout_source != nullptr) || (m_interval == 0)) return; m_timeout_source = g_timeout_source_new(250); g_source_set_callback(m_timeout_source, static_timeout_cb, gpointer(this), nullptr); g_source_attach(m_timeout_source, nullptr); } void stop_timeout() { if (m_timeout_source == nullptr) return; g_source_destroy(m_timeout_source); g_source_unref(m_timeout_source); m_timeout_source = nullptr; } void advance_uri() { m_cur_uri++; if (m_cur_uri == m_uris.end()) m_cur_uri = m_uris.begin(); } void move_to_next_video() { gst_element_set_state(m_pipeline, GST_STATE_READY); advance_uri(); std::cerr << "=========== next URI: " << m_cur_uri->c_str() << " ===========\n"; g_object_set(G_OBJECT(m_pipeline), "uri", m_cur_uri->c_str(), nullptr); gst_element_set_state(m_pipeline, GST_STATE_PLAYING); } void update_duration() { gint64 duration; gboolean success = gst_element_query_duration(GST_ELEMENT(m_pipeline), GST_FORMAT_TIME, &duration); m_duration = success ? duration : -1; } static gboolean static_timeout_cb(gpointer p_user_data) { video_carousel *self = static_cast < video_carousel* > (p_user_data); gint64 cur_pos = self->get_current_position(); if (cur_pos != -1) { if ((cur_pos) >= (GST_MSECOND * self->m_interval)) { GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(self->m_pipeline)); gst_bus_post( bus, gst_message_new_application( GST_OBJECT(self->m_pipeline), gst_structure_new_empty("switch") ) ); gst_object_unref(GST_OBJECT(bus)); } } return G_SOURCE_CONTINUE; } static gboolean static_bus_watch(GstBus *, GstMessage *p_msg, gpointer p_user_data) { video_carousel *self = static_cast < video_carousel* > (p_user_data); switch (GST_MESSAGE_TYPE(p_msg)) { case GST_MESSAGE_STATE_CHANGED: { if (GST_MESSAGE_SRC(p_msg) != GST_OBJECT(self->m_pipeline)) break; GstState old_gstreamer_state, new_gstreamer_state, pending_gstreamer_state; gst_message_parse_state_changed( p_msg, &old_gstreamer_state, &new_gstreamer_state, &pending_gstreamer_state ); std::string dot_fn = std::string("pipeline") + "__old-" + gst_element_state_get_name(old_gstreamer_state) + "__new-" + gst_element_state_get_name(new_gstreamer_state) + "__pending-" + gst_element_state_get_name(pending_gstreamer_state); GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(self->m_pipeline), GST_DEBUG_GRAPH_SHOW_ALL, dot_fn.c_str()); switch (new_gstreamer_state) { case GST_STATE_PAUSED: self->stop_timeout(); break; case GST_STATE_PLAYING: self->start_timeout(); break; default: break; } break; } case GST_MESSAGE_STREAM_START: self->m_duration = -1; self->update_duration(); break; case GST_MESSAGE_APPLICATION: { if (gst_message_has_name(p_msg, "switch")) self->move_to_next_video(); break; } case GST_MESSAGE_EOS: { self->move_to_next_video(); break; } case GST_MESSAGE_BUFFERING: { gint percent = 0; gst_message_parse_buffering(p_msg, &percent); if (percent < 100) gst_element_set_state(self->m_pipeline, GST_STATE_PAUSED); else gst_element_set_state(self->m_pipeline, GST_STATE_PLAYING); break; } case GST_MESSAGE_LATENCY: gst_bin_recalculate_latency(GST_BIN(self->m_pipeline)); break; case GST_MESSAGE_REQUEST_STATE: { GstState requested_state; gst_message_parse_request_state(p_msg, &requested_state); gst_element_set_state(self->m_pipeline, requested_state); break; } case GST_MESSAGE_DURATION: self->update_duration(); break; case GST_MESSAGE_INFO: case GST_MESSAGE_WARNING: case GST_MESSAGE_ERROR: { GError *error = nullptr; gchar *debug_info = nullptr; gchar const *desc; switch (GST_MESSAGE_TYPE(p_msg)) { case GST_MESSAGE_INFO: desc = "info"; gst_message_parse_info(p_msg, &error, &debug_info); break; case GST_MESSAGE_WARNING: desc = "warning"; gst_message_parse_warning(p_msg, &error, &debug_info); break; case GST_MESSAGE_ERROR: { desc = "error"; gst_message_parse_error(p_msg, &error, &debug_info); GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(self->m_pipeline), GST_DEBUG_GRAPH_SHOW_ALL, "pipeline-error"); g_main_loop_quit(self->m_loop); break; } default: break; } std::cerr << "GStreamer " << desc; if (error != nullptr) std::cerr << " message: " << error->message; else std::cerr << " "; if (debug_info != nullptr) std::cerr << " " << debug_info; else std::cerr << " "; std::cerr << std::endl; if (error != nullptr) g_error_free(error); if (debug_info != nullptr) g_free(debug_info); break; } default: break; } return TRUE; } GstElement *m_pipeline; uris m_uris; uris::iterator m_cur_uri; guint m_interval; GMainLoop *m_loop; gint64 m_duration; GSource *m_timeout_source; }; int main(int argc, char *argv[]) { GError *error = nullptr; if (!gst_init_check(&argc, &argv, &error)) { std::cerr << "Could not initialize GStreamer: " << error->message << "\n"; return -1; } GMainLoop *loop = nullptr; try { guint interval = 0; std::string video_sink, audio_sink; int c; while ((c = getopt(argc, argv, "i:v:a:h")) != -1) { switch (c) { case 'i': interval = std::stoul(optarg); break; case 'v': video_sink = optarg; break; case 'a': audio_sink = optarg; break; case 'h': std::cerr << "Usage: " << argv[0] << " [OPTION]... [URI/FILE]...\n" "Plays a list of videos specified via URI or filename in a loop,\n" "optionally with a fixed interval.\n" "\n" "Options:\n" " -h This help\n" " -i INTERVAL Use fixed interval of INTERVAL milliseconds.\n" " Default value 0 plays the videos until they end.\n" " -v VIDEOSINK Use the specified GStreamer video sink element.\n" " Default: let GStreamer pick one automatically\n" " -a AUDIOSINK Use the specified GStreamer audio sink element.\n" " Default: let GStreamer pick one automatically\n" ; return -1; default: break; } } if (optind >= argc) { std::cerr << "At least one URI/filename must be specified\n"; return -1; } video_carousel::uris uris; while (optind < argc) { std::string uri = argv[optind++]; if (!gst_uri_is_valid(uri.c_str())) { error = nullptr; gchar *uri_cstr = gst_filename_to_uri(uri.c_str(), &error); if (uri_cstr != nullptr) { uri = uri_cstr; g_free(uri_cstr); } if (error != nullptr) { std::string message = error->message; g_error_free(error); throw std::invalid_argument(message); } } uris.push_back(uri); } loop = g_main_loop_new(nullptr, TRUE); g_unix_signal_add(SIGINT, sigint, loop); video_carousel carousel(uris, interval, video_sink, audio_sink, loop); carousel.start(); g_main_loop_run(loop); } catch (std::exception const &p_exc) { std::cerr << "Exception raised: " << p_exc.what() << "\n"; } if (loop != nullptr) g_main_loop_unref(loop); std::cerr << "Quitting\n"; return 0; }