Overview

XB already has

  1. json-schema-definitions://experience_builder.module/image, which allows rendering an image
  2. json-schema-definitions://experience_builder.module/image-uri, which allows linking an image

The 4 3 missing $refs to use in SDCs' schemas:

  1. linking a video
  2. rendering ("embedding") a video
  3. linking a document
  4. linking and rendering ("embedding") a document 👈 (most) browsers can only natively render PDFs, all other document types require specialized embedding widgets — tackling that seems out of scope here?

Note that in core, the:

  • "document" media type allows txt rtf doc docx ppt pptx xls xlsx pdf odf odg odp ods odt fodt fods fodp fodg key numbers pages
  • "video" media type allows mp4
  • "remote video" media type allows YouTube + Vimeo

Proposed resolution

For the 3 missing concepts:

  1. Challenge: more often than not, you wouldn't link to a .mp4, .webm … URL, but you'd link to YouTube or Vimeo or …. How do we express that? 🤔 Seems like we'd need to draw inspiration from core's \Drupal\media\OEmbed\Resource, which has ::TYPE_LINK, ::TYPE_PHOTO, ::TYPE_RICH and ::TYPE_VIDEO.
  2. This pretty much requires an oembed URL, and then the oembed fetching to happen … inside the SDC itself? 🤔
    I propose:
        test_string_embedded_video:
          title: 'Embedded video'
          type: string
          contentMediaType: application/oembed+json
          x-oembed-type: video
          x-oembed-maxwidth: 600
          x-oembed-maxheight: 400
    

    (This is to specify this would expect the video type of oEmbed, which . Supporting other oEmbed types would be possible later, but out of scope.)

  3. Linking to a document seems simple enough:
        "document-uri": {
          "title": "Document URL",
          "type": "string",
          "format": "uri-reference",
          "pattern": "^(/|https?://)?.*\\.(txt|rtf|doc|docx|ppt|pptx|xls|xlsx|pdf|odf|odg|odp|ods|odt|fodt|fods|fodp|fodg|key|numbers|pages)(\\?.*)?(#.*)?$"
        },
    

User interface changes

TBD

Issue fork canvas-3524130

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

wim leers created an issue. See original summary.

wim leers’s picture

Assigned: Unassigned » lauriii
Issue tags: +Needs product manager review

@lauriii, do you agree that linking and rendering ("embedding") a document does not make sense to support, and the 3 remaining missing $refs in the issue summary would address all needs?

wim leers’s picture

wim leers’s picture

Issue summary: View changes

@lauriii, also see the issue summary: do you want only (oEmbed) remote video, or also local video URLs?

Added more detail to the oEmbed video proposal.

lauriii’s picture

Assigned: lauriii » wim leers
Status: Active » Needs review
Issue tags: -Needs product manager review

I think we should express the uploaded videos as follows in schema.json:

{
  "video": {
    "title": "video",
    "type": "object",
    "required": ["src"],
    "properties": {
      "src": {
        "title": "Video URL",
        "type": "string",
        "format": "uri"
      },
      "poster": {
        "title": "Poster image URL",
        "$ref": "json-schema-definitions://experience_builder.module/image-uri"
      },
      "width": {
        "title": "Video width",
        "type": "integer"
      },
      "height": {
        "title": "Video height",
        "type": "integer"
      },
    }
  }
}

For oEmbed, I'm not sure we need the max width / max height here. It seems something that the component itself should handle? We may need to expose the height / width of the resource, as well as title which would make this shape as follows in schema.json:

{
  "oembed_video": {
    "title": "oembed_video",
    "type": "object",
    "required": ["url"],
    "properties": {
      "url": {
        "title": "oEmbed URL",
        "type": "string",
        "format": "uri",
        "contentMediaType": "application/oembed+json",
        "x-oembed-type": "video"
      },
      "width": {
        "title": "Video width",
        "type": "integer"
      },
      "height": {
        "title": "Video height",
        "type": "integer"
      },
      "title": {
        "title": "Video title",
        "type": "string"
      }
    }
  }
}

For documents, to display a link, we'd need a bit more than just the URI in the schema.json:

{
  "document": {
    "title": "document",
    "type": "object",
    "required": ["src"],
    "properties": {
      "src": {
        "title": "Document URL",
        "$ref": "json-schema-definitions://experience_builder.module/document-uri"
      },
      "title": {
        "title": "Document title",
        "type": "string"
      },
      "description": {
        "title": "Document description",
        "type": "string"
      },
      "filename": {
        "title": "Filename",
        "type": "string"
      },
      "filesize": {
        "title": "File size",
        "type": "integer",
        "description": "File size in bytes"
      },
      "mimetype": {
        "title": "MIME type",
        "type": "string"
      }
    },
    "document-uri": {
      "title": "Document URL",
      "type": "string",
      "format": "uri-reference",
      "pattern": "^(/|https?://)?.*\\.(txt|rtf|doc|docx|ppt|pptx|xls|xlsx|pdf|odf|odg|odp|ods|odt|fodt|fods|fodp|fodg|key|numbers|pages)(\\?.*)?(#.*)?$"
    }
  },
}

Edit: I've removed the media controls and loop from the schema. Those should be handled manually outside of the schema since they would not be usually stored alongside with the video.

wim leers’s picture

Assigned: wim leers » f.mazeikis
Status: Needs review » Active

Walked @f.mazeikis through the Shape matching infrastructure. He's gonna get started on implementing the new json-schema-definitions://experience_builder.module/video object shape that @lauriii proposed in #5 👍

f.mazeikis made their first commit to this issue’s fork.

f.mazeikis’s picture

Assigned: f.mazeikis » lauriii

@lauriii

In #5 you've proposed shapes for video and oembed_video and I have some questions.

  1. For video you've proposed poster property - as @ penyaskito pointed out on the draft MR - the commonly used term (and already existing property in Drupal Media API) for poster would be thumbnail. This also applies to Youtube, vimeo and others. Is there a good reason we would not use thumbnail as terminology and the actual Drupal Media thumbnail property?
  2. Properties for height and width for video are not currently stored for video_file source, we could alter the source and add missing properties via hook_media_source_info_alter - is this what you would expect?
  3. For oembed_video described properties could be fetched from oEmbed resource provider (Youtube or Vimeo) - Media entity itself seems to fetch those at the runtime. Do you expect us to fetch the values and store/own them as prop inputs? Fetch the values and use them, but not store them? Do you want users to be able to provide their own title, width and height, that would override whatever oEmbed provider supplies?

Depending on the answer to 3 it might look quite similar to 2 - just trying to figure out what the expectation is here, before I write a bunch of code.

wim leers’s picture

Assigned: lauriii » f.mazeikis

#9:

  1. <video poster> is not semantically the same as \Drupal\media\Entity\Media::loadThumbnail().

    See https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/poster and contrast with core/modules/image/config/install/image.style.thumbnail.yml.

    The media thumbnail is a (typically square) image used when browsing the media library. You cannot use this for <video poster: the aspect ratio would be wrong, and the quality is very likely far too low.

    Conclusion: without the ability to extract an image from a video (we definitely can't do that — Drupal's media system doesn't even do this), we must make do without a poster. Which is fine, because as you can see: poster is optional!

    Conclusion: just do not populate poster by default 👍

  2. Same thing here — they're both optional. Since core's "Video" media type doesn't support it, I don't see how XB could support it.

    ⚠️ ⚠️ ⚠️ OTOH — what you're dealing with here is that <video width> refers to the video display area, not the video's resolution.

    I bet that this is something @lauriii would like to see supported — even if you could enter bad values (e.g. specifying width/height not respecting aspect ratio etc.), because it impacts the content authoring experience a lot.

    However, given the current reality of XB only allowing to use StaticPropSources to populate SDC/code component props, this technically requires a field type + widget that provides src (media library) + width/height (separate static inputs). This is what AdaptedPropSource was intended for, but XB's UI doesn't support this yet.

    Conclusion: we don't allow width and height to be entered either, because we cannot support it yet. Here too, that's possible, because both are optional 👍

  3. Do you expect us to fetch the values and store/own them as prop inputs? Fetch the values and use them, but not store them? Do you want users to be able to provide their own title, width and height, that would override whatever oEmbed provider supplies?

    Here too, everything I wrote in response to #9.1 and #9.2 applies: width and height are not the ones you seem to think these are referring to (and would require AdaptedPropSource in the UI), and url is the only one that is required.

tl;dr: forget about all optional key-value pairs, limit scope here to only the required ones, because they're the only ones that are feasible.

effulgentsia’s picture

Issue tags: +sprint
f.mazeikis’s picture

Assigned: f.mazeikis » wim leers
Status: Active » Needs review

Implemented feedback from #10, updated simple/video SDC component and added new simple/ombed_video SDC component. This was useful for testing via UI, but not quite sure if we want to keep it.
Added comments to MR, moving to needs review.

wim leers’s picture

YAYAYAY!

First local video from Drupal's Media Library rendered via a component:

First remote video (about XB of course — Lee's talk on YouTube!) rendered via a component:

… unfortunately some kind of issue with YouTube refusing things, probably CSP-related, we can figure that out next 👍

wim leers’s picture

Status: Needs review » Needs work

Found the culprit. oEmbed videos are passed as-is:

<iframe src="https://www.youtube.com/watch?v=v747p7xEcgg" width="" height="" title="" data-xb-uuid="a9a2498e-e1a1-4464-b2ea-db72daf7ea60"></iframe>

… which is incorrect.

This should actually render something like

<iframe src="https://example.com/media/oembed?url=https%3A//www.youtube.com/watch%3Fv%3Dv747p7xEcgg&max_width=0&max_height=0&hash=2kCbWsOQ7urG-UZLzC9KUBuT1B-7jnlX66TsoG_WL7g" width="" height="" title="" data-xb-uuid="a9a2498e-e1a1-4464-b2ea-db72daf7ea60"></iframe>

Why? Because that's how core renders remote videos successfully. See for yourself by doing:

  1. Go to /admin/config/media/media-settings and enable stand-alone media URLs
  2. Create a video media entity for some YouTube video
  3. Navigate to /media/N
  4. Video appears!
  5. Put a breakpoint in OEmbedFormatter::viewElements(), observe the different steps, specifically how $url and $resource_url exist. This results in:
    array (
      '#type' => 'html_tag',
      '#tag' => 'iframe',
      '#attributes' => 
      array (
        'src' => 'http://core.test/media/oembed?url=https%3A//www.youtube.com/watch%3Fv%3Dv747p7xEcgg&max_width=0&max_height=0&hash=2kCbWsOQ7urG-UZLzC9KUBuT1B-7jnlX66TsoG_WL7g',
        'scrolling' => false,
        'width' => 200,
        'height' => 113,
        'class' => 
        array (
          0 => 'media-oembed-content',
        ),
        'loading' => 'lazy',
      ),
      '#attached' => 
      array (
        'library' => 
        array (
          0 => 'media/oembed.formatter',
        ),
      ),
    )

I think this MR needs to perform that
https://www.youtube.com/watch?v=v747p7xEcgg
👇

http://core.test/media/oembed?url=https%3A//www.youtube.com/watch%3Fv%3Dv747p7xEcgg&max_width=0&max_height=0&hash=2kCbWsOQ7urG-UZLzC9KUBuT1B-7jnlX66TsoG_WL7g

transformation, by adding a new computed property on the field definitions for the oEmbed-using MediaType bundles of the Media entity type. That can be done using hook_entity_bundle_field_info_alter(). The MR already needs that for something else too: https://git.drupalcode.org/project/experience_builder/-/merge_requests/1...

(The hash is coming from \Drupal\media\IFrameUrlHelper::getHash(), the rest from \Drupal\media\OEmbed\UrlResolver::getResourceUrl().)

This would also result in \hook_oembed_resource_url_alter() etc firing (see the example implementation: it rewrites all YouTube URLs to the cookieless variant!)

wim leers’s picture

wim leers’s picture

Assigned: wim leers » f.mazeikis

This needs work for:

  1. multiple remarks on the MR
  2. but most importantly because it's not yet resolving an author-provided URL to the corresponding oEmbed URL, which is why remote videos refuse to render

Note: I'd be totally fine with splitting this across multiple MRs, so you could already land the "local video" MR immediately.

Finally: this is not yet handling document links.

wim leers’s picture

Priority: Normal » Major
wim leers’s picture

Assigned: f.mazeikis » wim leers
wim leers’s picture

  1. StaticPropSource matching was already working 👍
  2. DynamicPropSource matching was not yet working, in part due to core bugs. While this also doesn't work in HEAD for multi-bundle image media references, it's important that the shapes we define are known to be matchable to avoid painting ourselves into a corner by the time we work on ContentTemplates in the near (post-beta1) future. So, fixed that.
    There are still remaining caveats though, but that's definitely a pre-existing problem, and the most critical part (the video URL) is working 👍 Follow-up: #3533675: Avoid suggesting UNIX timestamp integers for `type: integer` props.
  3. The approach @f.mazeikis took for oEmbed videos was based on a suggestion by @lauriii: add a new Twig function. That's pragmatic, and makes things work, but … it also makes the subsequent issue #3528396: Video prop shape: e2e test coverage for video from media library the component instance form + fix revealed bug in evaluating `StaticPropSource`s impossible. So the solution is to do what we've done for the file_uri field type's url property or better yet: the wholly new url property we just added to the link field type in #3499279: Make link widget autocomplete work (for uri and uri-reference props).

The latter is what I'm currently working on, and is very unlikely to get finished in the next 1.5 hour (before the next and final sprint before beta1 starts).

So, being pragmatic: creating an MR to land just local video shape matching support, to unblock #3528396: Video prop shape: e2e test coverage for video from media library the component instance form + fix revealed bug in evaluating `StaticPropSource`s with at least local videos, because as of finishing point 2 above, that's completely working. Here it is in action in both the all-props SDC and the new video SDC:

wim leers’s picture

StatusFileSize
new1013.05 KB

Still had one major concern about the new experience_builder:video SDC, because it conflated:

  1. intrinsic width: width of the video itself — a fact
  2. display width: the width the content author would like the video to be displayed at — a subjective decision

See the details.

So, made that distinction a reality at all levels, resulting in this more capable UX (previously no resizing was possible):

wim leers’s picture

The change I made in #21 introduces a new prop: display_width. It uses type: integer, minimum: 1.

This caused 2 test failures:

Drupal\Tests\experience_builder\Kernel\Config\ComponentTest::testComponentAutoUpdate
Drupal\Core\Config\Schema\SchemaIncompleteException: Schema errors for experience_builder.component.sdc.experience_builder.video with the following errors: 0 [active_version] The version da2ffec495563dd6 does not match the hash of the settings for this version, expected facbb275ecb9699a.

+

Drupal\Tests\experience_builder\Kernel\MediaLibraryHookStoragePropAlterTest::testUniquePropSchemaDiscovery
Drupal\Core\Config\Schema\SchemaIncompleteException: Schema errors for experience_builder.component.sdc.experience_builder.video with the following errors: 0 [active_version] The version 44d35b0af1837caf does not match the hash of the settings for this version, expected f828aa2fb0fe4c76.

Note that '' is literally the default value for max in \Drupal\Core\Field\Plugin\Field\FieldType\IntegerItem::defaultFieldSettings(). So the instance settings we're validating:

[
  "max": "",
  "min": 1,
]

Root cause:

  1. Component::save() eventually calls
  2. \Drupal\Core\Config\Config::save(has_trusted_data: FALSE), which eventually calls
  3. \Drupal\Core\Config\StorableConfigBase::castValue(key: 'versioned_properties.active.settings.prop_field_definitions.display_width.field_instance_settings.max', value: '')
  4. which contains this beauty:
            // Special handling for integers and floats since the configuration
            // system is primarily concerned with saving values from the Form API
            // we have to special case the meaning of an empty string for numeric
            // types. In PHP this would be casted to a 0 but for the purposes of
            // configuration we need to treat this as a NULL.
            $empty_value = $value === '' && ($element instanceof IntegerInterface || $element instanceof FloatInterface);
    
            if ($value === NULL || $empty_value) {
              $value = NULL;
            }
    

    😬😬😬😬😬😬😬😬

wim leers’s picture

  • wim leers committed 2932feb8 on 0.x
    Issue #3524130 by f.mazeikis, wim leers, lauriii: Define JSON Schema $...
lauriii’s picture

Issue tags: -beta blocker

Discussed with @Wim Leers about the challenges with OEmbed videos. Decided that we should descope this from beta so that we have more time to think through the right implementation for this. In the meanwhile, this can be work around by building custom components with a URL prop that references the remote video.

effulgentsia’s picture

I opened #3534599: Change video prop shape: remove `width` and `height` as a follow-up based on discussion with @lauriii and @balintbrews.

wim leers’s picture

Assigned: wim leers » Unassigned
Issue tags: -sprint
Related issues: -#3534599: Change video prop shape: remove `width` and `height`
larowlan’s picture

Could we get an issue summary update listing the challenges that were discussed that lead to #25 - thanks!

wim leers’s picture

Assigned: Unassigned » wim leers

Yes, definitely — agreed that's very much necessary 🙈

Project: Experience Builder » Drupal Canvas
Version: 0.x-dev » 1.x-dev

Experience Builder has been renamed to Drupal Canvas in preparation for its beta release. You can now track issues on the new project page.

wim leers’s picture

heyyo’s picture

Any plan to support media audio file too ?

wim leers’s picture

Assigned: wim leers » f.mazeikis

Felix, please update the issue summary to explain how you intend to overcome the oEmbed-related challenges. 🙏 See #28 & #25.

f.mazeikis’s picture

On a call @lauriii clarified that initial set of document types we need to support is as follows:

File format File extension
Excel* .xls, .xlsx
Illustrator .ai
InDesign .indd
Keynote Presentation* .key
Portable Document Format* .pdf
PowerPoint* .ppt, .pptm, .pptx
Word* .doc, .docx
wim leers’s picture

Status: Needs work » Postponed (maintainer needs more info)
Related issues: +#3567249: oEmbed media source plugin should not use the "string" field type

AFAICT oEmbed support is impossible until we do #3567249: oEmbed media source plugin should not use the "string" field type. Or, alternatively, we'd need to:

  1. add a whole range of adapter plugins that mimick what core's oEmbed formatter, resource fetcher and URL resolver do
  2. and then automatically use them for relevant prop shapes, similar to how #3563380: Allow linking integer timestamps to `type: string, format: date`: allow `DynamicPropSource` to optionally use a single-input adapter plugin hard-coded an adapter for the timestamp|created|changed field types.