Skip to content

Conversation

@latin-panda
Copy link
Collaborator

@latin-panda latin-panda commented Jan 8, 2026

Closes #607 #608

I have verified this PR works in these browsers (latest versions):

  • Chrome
  • Firefox
  • Safari (macOS)
  • Safari (iOS)
  • Chrome for Android
  • Not applicable

What else has been done to verify that this works as intended?

Test mobile: Label with media
Test desktop: Label with media
Regression test mobile: image upload
Regression test desktop: image upload
Regression test mobile: select with image
Regression test desktop: select with image

Why is this the best possible solution? Were any other approaches considered?

The engine already supported video and audio within iText elements used for labels and <select> options. This PR focuses on building support in the client side (Vue app)

It reuses the existing logic for images and introduces a new base component called MediaBlockBase, which is responsible for:

  • Fetching the media file
  • Handling loading and error states
  • Providing a slot for the actual media rendering

We now have three specialized components, one for each media type, that implement MediaBlockBase, receive the file, and handle rendering:

  • ImageBlock
    Reuses the pre-existing logic that determines whether an image is "small" and adjusts its display accordingly

  • VideoBlock
    Renders the browser's native <video> player

  • AudioBlock
    Renders the browser's native <audio> player
    Additionally provides play/stop controls when only the icon is displayed (without the full player UI)

The native browser players are used with their default styling (which varies between browsers).
This is intentional for the current implementation as we aimed for the leanest possible solution.
A custom player may be integrated in the future.

The autoplay will be implemented as part of another ticket once the pagination has been implemented.

How does this change affect users? Describe intentional changes to behavior and behavior that could have accidentally been affected by code changes. In other words, what are the regression risks?

None. The images should continue working the same way. A regression test has been done in: labels, select options, and the upload question type.

Do we need any specific form for testing your changes? If so, please attach one.

The PR includes a test form.

What's changed

  • Updated the common package to support .mp4 and .mp3 attachments. This is only for allowing test forms to find the attachment and render it (for local development and demo site on the website)
  • Extract common logic from ImageBlock to MediaBlockBase to reuse it for audio and video.

@changeset-bot
Copy link

changeset-bot bot commented Jan 8, 2026

🦋 Changeset detected

Latest commit: e01b923

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@getodk/web-forms Minor
@getodk/common Minor
@getodk/xpath Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@latin-panda latin-panda linked an issue Jan 8, 2026 that may be closed by this pull request
8 tasks
const parentPath = localPath.replace(/\/[^/]+$/, '');

service.activateFixtures(parentPath, ['file', 'file-csv', 'images']);
service.activateFixtures(parentPath, ['file', 'file-csv', 'images', 'audio', 'video']);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes in this file are to support audio and video in test forms (local dev and demo page on the website)

this.mimeType = 'image/svg+xml';
break;

case '.mp4':
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes in this file are to support mp3 and mp4 in test forms (local dev and demo page on the website)

align-content: flex-start;
flex-wrap: wrap;

:deep(.media-content) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

align video and audio media

flex-direction: column;
align-items: center;
justify-content: flex-start;
gap: 10px;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding space in case of more than one media (edge case) in the same option

import { type ObjectURL } from '@getodk/common/lib/web-compat/url.ts';
import { computed, ref, triggerRef } from 'vue';
defineProps<{
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly preexisting code that was split between ImageBlock and MediaBlockBase

'broken-file': errorMessage?.length,
}"
>
<slot
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly preexisting code from the original ImageBlock component, with a slot element added to pass the file and render it.

@change="$emit('change')"
/>
<TextMedia :label="option.label" />
<TextMedia :label="option.label" :audio-icons-only="question.currentState.isSelectWithImages" />
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The engine detects if a select contains at least one image, and it displays differently from regular selects (each option is a card). Selects with images only show the play and stop buttons for audio, positioned next to the label, while videos are displayed in the default browser player.

@latin-panda latin-panda marked this pull request as ready for review January 12, 2026 13:19
@latin-panda
Copy link
Collaborator Author

@sadiqkhoja This is ready for review. Let me know any questions. Thanks for the help!

Copy link
Contributor

@sadiqkhoja sadiqkhoja left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good. I have added couple of questions for clarification, none of them should block this PR

import { ref } from 'vue';
defineProps<{
readonly resourceUrl?: JRResourceURL;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can resourceUrl ever be null? Looking at its usage in TextMedia and ControlLabel, I am not sure if typescript is smart enough to know that if resourceUrl is null then component is not rendered at all.

same comment applies to ImageBlock and VideoBlock

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! This line was incorrectly copied from the image component. The audio and video should have a defined resourceURL (I've removed the ? operator). If it is undefined or null, the component won't display. It's considered that no media were defined.

The image component renders from a resource URL or a blob.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for video for question labels Add support for audio for question labels

3 participants