diff --git a/docs/gst/gstreamer-sections.txt b/docs/gst/gstreamer-sections.txt index 4d991084a1..f8f8a0ff02 100644 --- a/docs/gst/gstreamer-sections.txt +++ b/docs/gst/gstreamer-sections.txt @@ -1678,6 +1678,11 @@ gst_message_streams_selected_add gst_message_streams_selected_get_size gst_message_streams_selected_get_stream +gst_message_new_redirect +gst_message_add_redirect_entry +gst_message_parse_redirect_entry +gst_message_get_num_redirect_entries + GstMessageClass GST_MESSAGE diff --git a/gst/gstmessage.c b/gst/gstmessage.c index a0b89b2b03..2ee64ea799 100644 --- a/gst/gstmessage.c +++ b/gst/gstmessage.c @@ -109,6 +109,7 @@ static GstMessageQuarks message_quarks[] = { {GST_MESSAGE_PROPERTY_NOTIFY, "property-notify", 0}, {GST_MESSAGE_STREAM_COLLECTION, "stream-collection", 0}, {GST_MESSAGE_STREAMS_SELECTED, "streams-selected", 0}, + {GST_MESSAGE_REDIRECT, "redirect", 0}, {0, NULL, 0} }; @@ -2914,3 +2915,243 @@ gst_message_parse_streams_selected (GstMessage * message, gst_structure_id_get (GST_MESSAGE_STRUCTURE (message), GST_QUARK (COLLECTION), GST_TYPE_STREAM_COLLECTION, collection, NULL); } + +/** + * gst_message_new_redirect: + * @src: The #GstObject whose property changed (may or may not be a #GstElement) + * @location: (transfer none): location string for the new entry + * @tag_list: (transfer full) (allow-none): tag list for the new entry + * @entry_struct: (transfer full) (allow-none): structure for the new entry + * + * Creates a new redirect message and adds a new entry to it. Redirect messages + * are posted when an element detects that the actual data has to be retrieved + * from a different location. This is useful if such a redirection cannot be + * handled inside a source element, for example when HTTP 302/303 redirects + * return a non-HTTP URL. + * + * The redirect message can hold multiple entries. The first one is added + * when the redirect message is created, with the given location, tag_list, + * entry_struct arguments. Use gst_message_add_redirect_entry() to add more + * entries. + * + * Each entry has a location, a tag list, and a structure. All of these are + * optional. The tag list and structure are useful for additional metadata, + * such as bitrate statistics for the given location. + * + * By default, message recipients should treat entries in the order they are + * stored. The recipient should therefore try entry #0 first, and if this + * entry is not acceptable or working, try entry #1 etc. Senders must make + * sure that they add entries in this order. However, recipients are free to + * ignore the order and pick an entry that is "best" for them. One example + * would be a recipient that scans the entries for the one with the highest + * bitrate tag. + * + * The specified location string is copied. However, ownership over the tag + * list and structure are transferred to the message. + * + * Returns: a newly allocated #GstMessage + * + * Since: 1.10 + */ +GstMessage * +gst_message_new_redirect (GstObject * src, const gchar * location, + GstTagList * tag_list, const GstStructure * entry_struct) +{ + GstStructure *structure; + GstMessage *message; + GValue entry_locations_gvalue = G_VALUE_INIT; + GValue entry_taglists_gvalue = G_VALUE_INIT; + GValue entry_structures_gvalue = G_VALUE_INIT; + + g_return_val_if_fail (location != NULL, NULL); + + g_value_init (&entry_locations_gvalue, GST_TYPE_LIST); + g_value_init (&entry_taglists_gvalue, GST_TYPE_LIST); + g_value_init (&entry_structures_gvalue, GST_TYPE_LIST); + + structure = gst_structure_new_id_empty (GST_QUARK (MESSAGE_REDIRECT)); + gst_structure_id_take_value (structure, GST_QUARK (REDIRECT_ENTRY_LOCATIONS), + &entry_locations_gvalue); + gst_structure_id_take_value (structure, GST_QUARK (REDIRECT_ENTRY_TAGLISTS), + &entry_taglists_gvalue); + gst_structure_id_take_value (structure, GST_QUARK (REDIRECT_ENTRY_STRUCTURES), + &entry_structures_gvalue); + + message = gst_message_new_custom (GST_MESSAGE_REDIRECT, src, structure); + g_assert (message != NULL); + + gst_message_add_redirect_entry (message, location, tag_list, entry_struct); + + return message; +} + +/** + * gst_message_add_redirect_entry: + * @message: a #GstMessage of type %GST_MESSAGE_REDIRECT + * @location: (transfer none): location string for the new entry + * @tag_list: (transfer full) (allow-none): tag list for the new entry + * @entry_struct: (transfer full) (allow-none): structure for the new entry + * + * Creates and appends a new entry. + * + * The specified location string is copied. However, ownership over the tag + * list and structure are transferred to the message. + * + * Since: 1.10 + */ +void +gst_message_add_redirect_entry (GstMessage * message, const gchar * location, + GstTagList * tag_list, const GstStructure * entry_struct) +{ + GValue val = G_VALUE_INIT; + GstStructure *structure; + GValue *entry_locations_gvalue; + GValue *entry_taglists_gvalue; + GValue *entry_structures_gvalue; + + g_return_if_fail (location != NULL); + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_REDIRECT); + + structure = GST_MESSAGE_STRUCTURE (message); + + entry_locations_gvalue = + (GValue *) gst_structure_id_get_value (structure, + GST_QUARK (REDIRECT_ENTRY_LOCATIONS)); + g_return_if_fail (GST_VALUE_HOLDS_LIST (entry_locations_gvalue)); + entry_taglists_gvalue = + (GValue *) gst_structure_id_get_value (structure, + GST_QUARK (REDIRECT_ENTRY_TAGLISTS)); + g_return_if_fail (GST_VALUE_HOLDS_LIST (entry_taglists_gvalue)); + entry_structures_gvalue = + (GValue *) gst_structure_id_get_value (structure, + GST_QUARK (REDIRECT_ENTRY_STRUCTURES)); + g_return_if_fail (GST_VALUE_HOLDS_LIST (entry_structures_gvalue)); + + g_value_init (&val, G_TYPE_STRING); + if (location) + g_value_set_string (&val, location); + gst_value_list_append_and_take_value (entry_locations_gvalue, &val); + + g_value_init (&val, GST_TYPE_TAG_LIST); + if (tag_list) + g_value_take_boxed (&val, tag_list); + gst_value_list_append_and_take_value (entry_taglists_gvalue, &val); + + g_value_init (&val, GST_TYPE_STRUCTURE); + if (entry_struct) + g_value_take_boxed (&val, entry_struct); + gst_value_list_append_and_take_value (entry_structures_gvalue, &val); +} + +/** + * gst_message_parse_redirect_entry: + * @message: a #GstMessage of type %GST_MESSAGE_REDIRECT + * @entry_index: index of the entry to parse + * @location: (out) (transfer none) (allow-none): return location for + * the pointer to the entry's location string, or %NULL + * @tag_list: (out) (transfer none) (allow-none): return location for + * the pointer to the entry's tag list, or %NULL + * @entry_struct: (out) (transfer none) (allow-none): return location + * for the pointer to the entry's structure, or %NULL + * + * Parses the location and/or structure from the entry with the given index. + * The index must be between 0 and gst_message_get_num_redirect_entries() - 1. + * Returned pointers are valid for as long as this message exists. + * + * Since: 1.10 + */ +void +gst_message_parse_redirect_entry (GstMessage * message, gsize entry_index, + const gchar ** location, GstTagList ** tag_list, + const GstStructure ** entry_struct) +{ + const GValue *val; + GstStructure *structure; + const GValue *entry_locations_gvalue; + const GValue *entry_taglists_gvalue; + const GValue *entry_structures_gvalue; + + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_REDIRECT); + + if (G_UNLIKELY (!location && !tag_list && !entry_struct)) + return; + + structure = GST_MESSAGE_STRUCTURE (message); + + entry_locations_gvalue = + gst_structure_id_get_value (structure, + GST_QUARK (REDIRECT_ENTRY_LOCATIONS)); + g_return_if_fail (GST_VALUE_HOLDS_LIST (entry_locations_gvalue)); + entry_taglists_gvalue = + gst_structure_id_get_value (structure, + GST_QUARK (REDIRECT_ENTRY_TAGLISTS)); + g_return_if_fail (GST_VALUE_HOLDS_LIST (entry_taglists_gvalue)); + entry_structures_gvalue = + gst_structure_id_get_value (structure, + GST_QUARK (REDIRECT_ENTRY_STRUCTURES)); + g_return_if_fail (GST_VALUE_HOLDS_LIST (entry_structures_gvalue)); + + if (location) { + val = gst_value_list_get_value (entry_locations_gvalue, entry_index); + g_return_if_fail (val != NULL); + *location = g_value_get_string (val); + } + + if (tag_list) { + val = gst_value_list_get_value (entry_taglists_gvalue, entry_index); + g_return_if_fail (val != NULL); + *tag_list = (GstTagList *) g_value_get_boxed (val); + } + + if (entry_struct) { + val = gst_value_list_get_value (entry_structures_gvalue, entry_index); + g_return_if_fail (val != NULL); + *entry_struct = (const GstStructure *) g_value_get_boxed (val); + } +} + +/** + * gst_message_get_num_redirect_entries: + * @message: a #GstMessage of type %GST_MESSAGE_REDIRECT + * + * Returns: the number of entries stored in the message + * + * Since: 1.10 + */ +gsize +gst_message_get_num_redirect_entries (GstMessage * message) +{ + GstStructure *structure; + const GValue *entry_locations_gvalue; + const GValue *entry_taglists_gvalue; + const GValue *entry_structures_gvalue; + gsize size; + + g_return_val_if_fail (GST_IS_MESSAGE (message), 0); + g_return_val_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_REDIRECT, 0); + + structure = GST_MESSAGE_STRUCTURE (message); + + entry_locations_gvalue = + gst_structure_id_get_value (structure, + GST_QUARK (REDIRECT_ENTRY_LOCATIONS)); + g_return_val_if_fail (GST_VALUE_HOLDS_LIST (entry_locations_gvalue), 0); + entry_taglists_gvalue = + gst_structure_id_get_value (structure, + GST_QUARK (REDIRECT_ENTRY_TAGLISTS)); + g_return_val_if_fail (GST_VALUE_HOLDS_LIST (entry_taglists_gvalue), 0); + entry_structures_gvalue = + gst_structure_id_get_value (structure, + GST_QUARK (REDIRECT_ENTRY_STRUCTURES)); + g_return_val_if_fail (GST_VALUE_HOLDS_LIST (entry_structures_gvalue), 0); + + size = gst_value_list_get_size (entry_locations_gvalue); + + g_return_val_if_fail ((size == + gst_value_list_get_size (entry_structures_gvalue)) + && (size == gst_value_list_get_size (entry_taglists_gvalue)), 0); + + return size; +} diff --git a/gst/gstmessage.h b/gst/gstmessage.h index 0fb0bf8be2..68213ebd1f 100644 --- a/gst/gstmessage.h +++ b/gst/gstmessage.h @@ -114,6 +114,9 @@ typedef struct _GstMessage GstMessage; * is available (Since 1.10) * @GST_MESSAGE_STREAMS_SELECTED: Message indicating the active selection of * #GstStreams has changed (Since 1.10) + * @GST_MESSAGE_REDIRECT: Message indicating to request the application to + * try to play the given URL(s). Useful if for example a HTTP 302/303 + * response is received with a non-HTTP URL inside. (Since 1.10) * @GST_MESSAGE_ANY: mask for all of the above messages. * * The different message types that are available. @@ -165,6 +168,7 @@ typedef enum GST_MESSAGE_PROPERTY_NOTIFY = GST_MESSAGE_EXTENDED + 3, GST_MESSAGE_STREAM_COLLECTION = GST_MESSAGE_EXTENDED + 4, GST_MESSAGE_STREAMS_SELECTED = GST_MESSAGE_EXTENDED + 5, + GST_MESSAGE_REDIRECT = GST_MESSAGE_EXTENDED + 6, GST_MESSAGE_ANY = (gint) (0xffffffff) } GstMessageType; @@ -623,6 +627,12 @@ void gst_message_parse_streams_selected (GstMessage * message, GstStr guint gst_message_streams_selected_get_size (GstMessage * message); GstStream *gst_message_streams_selected_get_stream (GstMessage *message, guint idx); +/* REDIRECT */ +GstMessage * gst_message_new_redirect (GstObject * src, const gchar * location, GstTagList * tag_list, const GstStructure * entry_struct) G_GNUC_MALLOC; +void gst_message_add_redirect_entry (GstMessage * message, const gchar * location, GstTagList * tag_list, const GstStructure * entry_struct); +void gst_message_parse_redirect_entry (GstMessage * message, gsize entry_index, const gchar ** location, GstTagList ** tag_list, const GstStructure ** entry_struct); +gsize gst_message_get_num_redirect_entries (GstMessage * message); + #ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstMessage, gst_message_unref) #endif diff --git a/gst/gstquark.c b/gst/gstquark.c index 88693454d2..c2a204db2b 100644 --- a/gst/gstquark.c +++ b/gst/gstquark.c @@ -73,7 +73,8 @@ static const gchar *_quark_strings[] = { "uri-redirection-permanent", "GstMessagePropertyNotify", "property-name", "property-value", "streams", "GstEventSelectStreams", "GstMessageStreamCollection", "collection", "stream", "stream-collection", - "GstMessageStreamsSelected" + "GstMessageStreamsSelected", "GstMessageRedirect", "redirect-entry-locations", + "redirect-entry-taglists", "redirect-entry-structures" }; GQuark _priv_gst_quark_table[GST_QUARK_MAX]; diff --git a/gst/gstquark.h b/gst/gstquark.h index dd0cde43de..fda5deb97e 100644 --- a/gst/gstquark.h +++ b/gst/gstquark.h @@ -212,7 +212,11 @@ typedef enum _GstQuarkId GST_QUARK_STREAM = 181, GST_QUARK_EVENT_STREAM_COLLECTION = 182, GST_QUARK_MESSAGE_STREAMS_SELECTED = 183, - GST_QUARK_MAX = 184 + GST_QUARK_MESSAGE_REDIRECT = 184, + GST_QUARK_REDIRECT_ENTRY_LOCATIONS = 185, + GST_QUARK_REDIRECT_ENTRY_TAGLISTS = 186, + GST_QUARK_REDIRECT_ENTRY_STRUCTURES = 187, + GST_QUARK_MAX = 188 } GstQuarkId; extern GQuark _priv_gst_quark_table[GST_QUARK_MAX]; diff --git a/tests/check/gst/gstmessage.c b/tests/check/gst/gstmessage.c index 77110ba405..9c8c27ab29 100644 --- a/tests/check/gst/gstmessage.c +++ b/tests/check/gst/gstmessage.c @@ -501,6 +501,106 @@ GST_START_TEST (test_parsing) gst_caps_unref (caps1); gst_caps_unref (caps2); } + /* GST_MESSAGE_REDIRECT */ + { + const gchar *parsed_location; + GstTagList *parsed_tag_list; + const GstStructure *parsed_structure; + const gchar *test_location = "some-location"; + const gchar *test_struct_name = "test-struct"; + const gchar *test_value_name = "foo"; + const gint test_value = 12345; + const guint test_bitrate = 120000; + gint value; + guint bitrate; + GstTagList *test_tag_list; + GstStructure *test_structure; + + test_structure = + gst_structure_new (test_struct_name, test_value_name, G_TYPE_INT, + test_value, NULL); + + /* Create a test tag list. It is ref'd before adding an entry to be able + * to test that new_redirect takes ownership */ + test_tag_list = gst_tag_list_new (GST_TAG_BITRATE, test_bitrate, NULL); + + /* Create the message and add the first entry, which only has a location + * and a tag list */ + gst_tag_list_ref (test_tag_list); + message = + gst_message_new_redirect (NULL, test_location, test_tag_list, NULL); + fail_if (message == NULL); + fail_unless (GST_MESSAGE_TYPE (message) == GST_MESSAGE_REDIRECT); + fail_unless (GST_MESSAGE_SRC (message) == NULL); + + /* Add the second entry, which only has a location and a structure */ + gst_message_add_redirect_entry (message, test_location, NULL, + gst_structure_copy (test_structure)); + + /* Add the third entry, which has a location, a taglist, and a structure */ + gst_tag_list_ref (test_tag_list); + gst_message_add_redirect_entry (message, test_location, test_tag_list, + gst_structure_copy (test_structure)); + + fail_unless (gst_message_get_num_redirect_entries (message) == 3); + + /* Check that the location of the first entry is correct and that the + * structure pointer is set to NULL */ + parsed_location = NULL; + parsed_tag_list = NULL; + parsed_structure = (const GstStructure *) 0x1; + gst_message_parse_redirect_entry (message, 0, &parsed_location, + &parsed_tag_list, &parsed_structure); + fail_unless (parsed_location != NULL); + fail_unless (parsed_tag_list != NULL); + fail_unless (parsed_structure == NULL); + fail_unless (!strcmp (parsed_location, test_location)); + fail_unless (gst_tag_list_get_uint (parsed_tag_list, GST_TAG_BITRATE, + &bitrate) && (bitrate == test_bitrate)); + + /* Check that the structure of the second entry is correct and that the + * tag list pointer is set to NULL */ + parsed_location = NULL; + parsed_tag_list = (GstTagList *) 0x1; + parsed_structure = NULL; + gst_message_parse_redirect_entry (message, 1, &parsed_location, + &parsed_tag_list, &parsed_structure); + fail_unless (parsed_location != NULL); + fail_unless (parsed_tag_list == NULL); + fail_unless (parsed_structure != NULL); + fail_unless (!strcmp (parsed_location, test_location)); + fail_unless (!strcmp (gst_structure_get_name (parsed_structure), + test_struct_name)); + fail_unless (gst_structure_get_int (parsed_structure, test_value_name, + &value) && (value == test_value)); + + /* Check that the location, tag list, and structure pointers of the + * third entry are correct */ + parsed_location = NULL; + parsed_tag_list = NULL; + parsed_structure = NULL; + gst_message_parse_redirect_entry (message, 2, &parsed_location, + &parsed_tag_list, &parsed_structure); + fail_unless (parsed_location != NULL); + fail_unless (parsed_tag_list != NULL); + fail_unless (parsed_structure != NULL); + fail_unless (!strcmp (parsed_location, test_location)); + fail_unless (!strcmp (gst_structure_get_name (parsed_structure), + test_struct_name)); + fail_unless (gst_tag_list_get_uint (parsed_tag_list, GST_TAG_BITRATE, + &bitrate) && (bitrate == test_bitrate)); + fail_unless (gst_structure_get_int (parsed_structure, test_value_name, + &value) && (value == test_value)); + + gst_message_unref (message); + + /* Since the message takes ownership over the tag list, its refcount + * must have been decreased after each added entry */ + fail_unless_equals_int (GST_MINI_OBJECT_REFCOUNT_VALUE (test_tag_list), 1); + + gst_structure_free (test_structure); + gst_tag_list_unref (test_tag_list); + } } GST_END_TEST; diff --git a/win32/common/libgstreamer.def b/win32/common/libgstreamer.def index 3770d163b7..ef9e8da50d 100644 --- a/win32/common/libgstreamer.def +++ b/win32/common/libgstreamer.def @@ -700,6 +700,8 @@ EXPORTS gst_memory_resize gst_memory_share gst_memory_unmap + gst_message_add_redirect_entry + gst_message_get_num_redirect_entries gst_message_get_seqnum gst_message_get_stream_status_object gst_message_get_structure @@ -728,6 +730,7 @@ EXPORTS gst_message_new_progress gst_message_new_property_notify gst_message_new_qos + gst_message_new_redirect gst_message_new_request_state gst_message_new_reset_time gst_message_new_segment_done @@ -765,6 +768,7 @@ EXPORTS gst_message_parse_qos gst_message_parse_qos_stats gst_message_parse_qos_values + gst_message_parse_redirect_entry gst_message_parse_request_state gst_message_parse_reset_time gst_message_parse_segment_done