static gboolean webkitMediaPlayReadyDecryptSinkEventHandler(GstBaseTransform* trans, GstEvent* event)
{
    gboolean result = FALSE;
    WebKitMediaPlayReadyDecrypt* self = WEBKIT_MEDIA_PLAYREADY_DECRYPT(trans);

    switch (GST_EVENT_TYPE(event)) {
    case GST_EVENT_PROTECTION: {
        const gchar* systemId;
        const gchar* origin;
        GstBuffer* initdatabuffer;

        GST_INFO_OBJECT(self, "received protection event");
        gst_event_parse_protection(event, &systemId, &initdatabuffer, &origin);
        GST_DEBUG_OBJECT(self, "systemId: %s", systemId);

        // Ignore protection events about content we cannot handle.
        if (!g_str_equal(systemId, PLAYREADY_PROTECTION_SYSTEM_ID)) {
            gst_event_unref(event);
            result = TRUE;
            break;
        }

        // Keep the event ref around so that the parsed event data
        // remains valid until the drm-key-need message has been sent.
        self->protectionEvent = event;
        self->initDataBuffer = initdatabuffer;
        g_timeout_add(0, requestKey, self);

        result = TRUE;
        break;
    }
    case GST_EVENT_CUSTOM_DOWNSTREAM_OOB: {
        GST_INFO_OBJECT(self, "received OOB event");
        g_mutex_lock(&self->mutex);
        const GstStructure* structure = gst_event_get_structure(event);
        if (gst_structure_has_name(structure, "dxdrm-session")) {
            GST_INFO_OBJECT(self, "received dxdrm session");

            const GValue* value = gst_structure_get_value(structure, "session");
            self->sessionMetaData = reinterpret_cast<WebCore::DiscretixSession*>(g_value_get_pointer(value));
            self->streamReceived = TRUE;
            g_cond_signal(&self->condition);
        }

        g_mutex_unlock(&self->mutex);
        gst_event_unref(event);
        result = TRUE;
        break;
    }
    default:
        result = GST_BASE_TRANSFORM_CLASS(parent_class)->sink_event(trans, event);
        break;
    }

    return result;
}
static void webkit_media_playready_decrypt_dispose(GObject* object)
{
    WebKitMediaPlayReadyDecrypt* self = WEBKIT_MEDIA_PLAYREADY_DECRYPT(object);

    if (self->protectionEvent) {
        gst_event_unref(self->protectionEvent);
        self->protectionEvent = nullptr;
    }

    g_mutex_clear(&self->mutex);
    g_cond_clear(&self->condition);

    G_OBJECT_CLASS(parent_class)->dispose(object);
}
static gboolean requestKey(gpointer userData)
{
    if (!WEBKIT_IS_MEDIA_PLAYREADY_DECRYPT(userData))
        return G_SOURCE_REMOVE;

    WebKitMediaPlayReadyDecrypt* self = WEBKIT_MEDIA_PLAYREADY_DECRYPT(userData);
    GST_DEBUG_OBJECT(self, "posting drm-key-needed message");
    gst_element_post_message(GST_ELEMENT(self),
        gst_message_new_element(GST_OBJECT(self),
            gst_structure_new("drm-key-needed", "data", GST_TYPE_BUFFER, self->initDataBuffer,
                "key-system-id", G_TYPE_STRING, "com.microsoft.playready", nullptr)));

    if (self->protectionEvent) {
        gst_event_unref(self->protectionEvent);
        self->protectionEvent = nullptr;
    }
    return G_SOURCE_REMOVE;
}
static GstStateChangeReturn webKitMediaPlayReadyDecryptChangeState(GstElement* element, GstStateChange transition)
{
    GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
    WebKitMediaPlayReadyDecrypt* self = WEBKIT_MEDIA_PLAYREADY_DECRYPT(element);

    switch (transition) {
    case GST_STATE_CHANGE_PAUSED_TO_READY:
        GST_DEBUG_OBJECT(self, "PAUSED->READY");
        g_mutex_lock(&self->mutex);
        g_cond_signal(&self->condition);
        g_mutex_unlock(&self->mutex);
        break;
    default:
        break;
    }

    ret = GST_ELEMENT_CLASS(parent_class)->change_state(element, transition);

    // Add post-transition code here.

    return ret;
}
static GstFlowReturn webkitMediaPlayReadyDecryptTransformInPlace(GstBaseTransform* base, GstBuffer* buffer)
{
    WebKitMediaPlayReadyDecrypt* self = WEBKIT_MEDIA_PLAYREADY_DECRYPT(base);
    GstFlowReturn result = GST_FLOW_OK;
    GstMapInfo map;
    const GValue* value;
    guint sampleIndex = 0;
    int errorCode;
    uint32_t trackID = 0;
    GstPad* pad;
    GstCaps* caps;
    GstMapInfo boxMap;
    GstBuffer* box = nullptr;
    GstProtectionMeta* protectionMeta = 0;
    gboolean boxMapped = FALSE;
    gboolean bufferMapped = FALSE;

    GST_TRACE_OBJECT(self, "Processing buffer");
    g_mutex_lock(&self->mutex);
    GST_TRACE_OBJECT(self, "Mutex acquired, stream received: %s", self->streamReceived ? "yes":"no");

    // The key might not have been received yet. Wait for it.
    if (!self->streamReceived)
        g_cond_wait(&self->condition, &self->mutex);

    if (!self->streamReceived) {
        GST_DEBUG_OBJECT(self, "Condition signaled from state change transition. Aborting.");
        result = GST_FLOW_NOT_SUPPORTED;
        goto beach;
    }

    GST_TRACE_OBJECT(self, "Proceeding with decryption");
    protectionMeta = reinterpret_cast<GstProtectionMeta*>(gst_buffer_get_protection_meta(buffer));
    if (!protectionMeta || !buffer) {
        if (!protectionMeta)
            GST_ERROR_OBJECT(self, "Failed to get GstProtection metadata from buffer %p", buffer);

        if (!buffer)
            GST_ERROR_OBJECT(self, "Failed to get writable buffer");

        result = GST_FLOW_NOT_SUPPORTED;
        goto beach;
    }

    bufferMapped = gst_buffer_map(buffer, &map, static_cast<GstMapFlags>(GST_MAP_READWRITE));
    if (!bufferMapped) {
        GST_ERROR_OBJECT(self, "Failed to map buffer");
        result = GST_FLOW_NOT_SUPPORTED;
        goto beach;
    }

    pad = gst_element_get_static_pad(GST_ELEMENT(self), "src");
    caps = gst_pad_get_current_caps(pad);
    if (g_str_has_prefix(gst_structure_get_name(gst_caps_get_structure(caps, 0)), "video/"))
        trackID = 1;
    else
        trackID = 2;
    gst_caps_unref(caps);
    gst_object_unref(pad);

    GST_TRACE_OBJECT(self, "Protection meta: %" GST_PTR_FORMAT, protectionMeta->info);

    if (gst_structure_get_uint(protectionMeta->info, "sample-index", &sampleIndex)) {
        // Process the PIFF box.
        value = gst_structure_get_value(protectionMeta->info, "box");
        if (!value) {
            GST_ERROR_OBJECT(self, "Failed to get encryption box for sample");
            result = GST_FLOW_NOT_SUPPORTED;
            goto beach;
        }

        box = gst_value_get_buffer(value);
        boxMapped = gst_buffer_map(box, &boxMap, GST_MAP_READ);
        if (!boxMapped) {
            GST_ERROR_OBJECT(self, "Failed to map encryption box");
            result = GST_FLOW_NOT_SUPPORTED;
            goto beach;
        }

        GST_TRACE_OBJECT(self, "decrypt sample %u", sampleIndex);
        if ((errorCode = self->sessionMetaData->decrypt(static_cast<void*>(map.data), static_cast<uint32_t>(map.size),
            static_cast<void*>(boxMap.data), static_cast<uint32_t>(boxMap.size), static_cast<uint32_t>(sampleIndex), trackID))) {
            GST_WARNING_OBJECT(self, "ERROR - packet decryption failed [%d]", errorCode);
            GST_MEMDUMP_OBJECT(self, "box", boxMap.data, boxMap.size);
            result = GST_FLOW_ERROR;
            goto beach;
        }
    } else {
        // Process CENC data.
        guint ivSize;
        gboolean encrypted;
        GstBuffer* ivBuffer = nullptr;
        GstMapInfo ivMap;
        unsigned position = 0;
        unsigned sampleIndex = 0;
        guint subSampleCount;
        GstBuffer* subsamplesBuffer = nullptr;
        GstMapInfo subSamplesMap;
        GstByteReader* reader = nullptr;

        if (!gst_structure_get_uint(protectionMeta->info, "iv_size", &ivSize)) {
            GST_ERROR_OBJECT(self, "failed to get iv_size");
            result = GST_FLOW_NOT_SUPPORTED;
            goto beach;
        }

        if (!gst_structure_get_boolean(protectionMeta->info, "encrypted", &encrypted)) {
            GST_ERROR_OBJECT(self, "failed to get encrypted flag");
            result = GST_FLOW_NOT_SUPPORTED;
            goto beach;
        }

        // Unencrypted sample.
        if (!ivSize || !encrypted)
            goto beach;

        if (!gst_structure_get_uint(protectionMeta->info, "subsample_count", &subSampleCount)) {
            GST_ERROR_OBJECT(self, "failed to get subsample_count");
            result = GST_FLOW_NOT_SUPPORTED;
            goto beach;
        }

        value = gst_structure_get_value(protectionMeta->info, "iv");
        if (!value) {
            GST_ERROR_OBJECT(self, "Failed to get IV for sample");
            result = GST_FLOW_NOT_SUPPORTED;
            goto beach;
        }

        ivBuffer = gst_value_get_buffer(value);
        if (!gst_buffer_map(ivBuffer, &ivMap, GST_MAP_READ)) {
            GST_ERROR_OBJECT(self, "Failed to map IV");
            result = GST_FLOW_NOT_SUPPORTED;
            goto beach;
        }

        if (subSampleCount) {
            value = gst_structure_get_value(protectionMeta->info, "subsamples");
            if (!value) {
                GST_ERROR_OBJECT(self, "Failed to get subsamples");
                result = GST_FLOW_NOT_SUPPORTED;
                goto release;
            }
            subsamplesBuffer = gst_value_get_buffer(value);
            if (!gst_buffer_map(subsamplesBuffer, &subSamplesMap, GST_MAP_READ)) {
                GST_ERROR_OBJECT(self, "Failed to map subsample buffer");
                result = GST_FLOW_NOT_SUPPORTED;
                goto release;
            }
        }

        reader = gst_byte_reader_new(subSamplesMap.data, subSamplesMap.size);
        if (!reader) {
            GST_ERROR_OBJECT(self, "Failed to allocate subsample reader");
            result = GST_FLOW_NOT_SUPPORTED;
            goto release;
        }

        GST_DEBUG_OBJECT(self, "position: %d, size: %d", position, map.size);
        while (position < map.size) {
            guint16 nBytesClear = 0;
            guint32 nBytesEncrypted = 0;

            if (sampleIndex < subSampleCount) {
                if (!gst_byte_reader_get_uint16_be(reader, &nBytesClear)
                    || !gst_byte_reader_get_uint32_be(reader, &nBytesEncrypted)) {
                    result = GST_FLOW_NOT_SUPPORTED;
                    GST_DEBUG_OBJECT(self, "unsupported");
                    goto release;
                }

                sampleIndex++;
            } else {
                nBytesClear = 0;
                nBytesEncrypted = map.size - position;
            }

            GST_TRACE_OBJECT(self, "%d bytes clear (todo=%d)", nBytesClear, map.size - position);
            position += nBytesClear;
            if (nBytesEncrypted) {
                GST_TRACE_OBJECT(self, "%d bytes encrypted (todo=%d)", nBytesEncrypted, map.size - position);
                if ((errorCode = self->sessionMetaData->processPayload(trackID, static_cast<const void*>(ivMap.data), static_cast<uint32_t>(ivMap.size), static_cast<void*>(map.data + position), static_cast<uint32_t>(nBytesEncrypted)))) {
                    GST_WARNING_OBJECT(self, "ERROR - packet decryption failed [%d]", errorCode);
                    result = GST_FLOW_ERROR;
                    goto release;
                }
                position += nBytesEncrypted;
            }
        }
    release:
        gst_buffer_unmap(ivBuffer, &ivMap);

        if (reader)
            gst_byte_reader_free(reader);

        if (subsamplesBuffer)
            gst_buffer_unmap(subsamplesBuffer, &subSamplesMap);
    }

beach:
    if (boxMapped)
        gst_buffer_unmap(box, &boxMap);

    if (bufferMapped)
        gst_buffer_unmap(buffer, &map);

    if (protectionMeta)
        gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta));

    GST_TRACE_OBJECT(self, "Unlocking mutex");
    g_mutex_unlock(&self->mutex);
    return result;
}
static GstFlowReturn webkitMediaPlayReadyDecryptTransformInPlace(GstBaseTransform* base, GstBuffer* buffer)
{
    WebKitMediaPlayReadyDecrypt* self = WEBKIT_MEDIA_PLAYREADY_DECRYPT(base);
    GstFlowReturn result = GST_FLOW_OK;
    GstMapInfo map;
    const GValue* value;
    guint sampleIndex = 0;
    int errorCode;
    uint32_t trackID = 0;
    GstPad* pad;
    GstCaps* caps;
    GstMapInfo boxMap;
    GstBuffer* box = nullptr;
    GstProtectionMeta* protectionMeta = 0;
    gboolean boxMapped = FALSE;
    gboolean bufferMapped = FALSE;

    GST_TRACE_OBJECT(self, "Processing buffer");
    g_mutex_lock(&self->mutex);
    GST_TRACE_OBJECT(self, "Mutex acquired, stream received: %s", self->streamReceived ? "yes":"no");

    // The key might not have been received yet. Wait for it.
    if (!self->streamReceived)
        g_cond_wait(&self->condition, &self->mutex);

    if (!self->streamReceived) {
        GST_DEBUG_OBJECT(self, "Condition signaled from state change transition. Aborting.");
        result = GST_FLOW_NOT_SUPPORTED;
        goto beach;
    }

    GST_TRACE_OBJECT(self, "Proceeding with decryption");
    protectionMeta = reinterpret_cast<GstProtectionMeta*>(gst_buffer_get_protection_meta(buffer));
    if (!protectionMeta || !buffer) {
        if (!protectionMeta)
            GST_ERROR_OBJECT(self, "Failed to get GstProtection metadata from buffer %p", buffer);

        if (!buffer)
            GST_ERROR_OBJECT(self, "Failed to get writable buffer");

        result = GST_FLOW_NOT_SUPPORTED;
        goto beach;
    }

    bufferMapped = gst_buffer_map(buffer, &map, static_cast<GstMapFlags>(GST_MAP_READWRITE));
    if (!bufferMapped) {
        GST_ERROR_OBJECT(self, "Failed to map buffer");
        result = GST_FLOW_NOT_SUPPORTED;
        goto beach;
    }

    pad = gst_element_get_static_pad(GST_ELEMENT(self), "src");
    caps = gst_pad_get_current_caps(pad);
    if (g_str_has_prefix(gst_structure_get_name(gst_caps_get_structure(caps, 0)), "video/"))
        trackID = 1;
    else
        trackID = 2;
    gst_caps_unref(caps);
    gst_object_unref(pad);

    if (!gst_structure_get_uint(protectionMeta->info, "sample-index", &sampleIndex)) {
        GST_ERROR_OBJECT(self, "failed to get sample-index");
        result = GST_FLOW_NOT_SUPPORTED;
        goto beach;
    }

    value = gst_structure_get_value(protectionMeta->info, "box");
    if (!value) {
        GST_ERROR_OBJECT(self, "Failed to get encryption box for sample");
        result = GST_FLOW_NOT_SUPPORTED;
        goto beach;
    }

    box = gst_value_get_buffer(value);
    boxMapped = gst_buffer_map(box, &boxMap, GST_MAP_READ);
    if (!boxMapped) {
        GST_ERROR_OBJECT(self, "Failed to map encryption box");
        result = GST_FLOW_NOT_SUPPORTED;
        goto beach;
    }

    GST_TRACE_OBJECT(self, "decrypt sample %u", sampleIndex);
    if ((errorCode = self->sessionMetaData->decrypt(static_cast<void*>(map.data), static_cast<uint32_t>(map.size),
        static_cast<void*>(boxMap.data), static_cast<uint32_t>(boxMap.size), static_cast<uint32_t>(sampleIndex), trackID))) {
        GST_WARNING_OBJECT(self, "ERROR - packet decryption failed [%d]", errorCode);
        GST_MEMDUMP_OBJECT(self, "box", boxMap.data, boxMap.size);
        result = GST_FLOW_ERROR;
        goto beach;
    }

beach:
    if (boxMapped)
        gst_buffer_unmap(box, &boxMap);

    if (bufferMapped)
        gst_buffer_unmap(buffer, &map);

    if (protectionMeta)
        gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta));

    GST_TRACE_OBJECT(self, "Unlocking mutex");
    g_mutex_unlock(&self->mutex);
    return result;
}