Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/plugins/visual-search/components/SearchButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import videojs from 'video.js';

export const SearchButton = (onClick) => {
const button = videojs.dom.createEl('button', {
className: 'vjs-visual-search-button',
title: 'Search video content'
className: 'vjs-control vjs-button vjs-visual-search-button',
title: 'Search video content',
ariaLabel: 'Search video content'
});

const searchIcon = videojs.dom.createEl('span', {
Expand Down
13 changes: 9 additions & 4 deletions src/plugins/visual-search/components/SearchInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ export const SearchInput = (onSearch, onClose) => {

const input = videojs.dom.createEl('input', {
className: 'vjs-visual-search-input',
type: 'text'
type: 'text',
ariaLabel: 'Search input',
tabIndex: -1
});

const closeButton = videojs.dom.createEl('button', {
className: 'vjs-visual-search-close',
className: 'vjs-control vjs-button vjs-visual-search-close',
type: 'button',
title: 'Close search'
title: 'Close search',
ariaLabel: 'Close search',
tabIndex: -1
});

// Add close icon
Expand Down Expand Up @@ -44,6 +48,7 @@ export const SearchInput = (onSearch, onClose) => {

return {
element: form,
input
input,
closeButton
};
};
49 changes: 34 additions & 15 deletions src/plugins/visual-search/components/SearchResults.js
Original file line number Diff line number Diff line change
@@ -1,51 +1,70 @@
import videojs from 'video.js';

export const SearchResults = (player) => {
export const SearchResults = player => {
const clearMarkers = () => {
player.$$('.vjs-visual-search-marker').forEach(el => el.remove());
player.$$('.vjs-visual-search-results-wrapper').forEach(el => el.remove());

// Remove the class that indicates search results are displayed
player.removeClass('vjs-visual-search-results-active');
};
const displayResults = (results) => {

const displayResults = results => {
// Clear existing markers
clearMarkers();

const total = player.duration();
const seekBar = player.controlBar.progressControl.seekBar;

// Create wrapper for search results
const wrapperEl = videojs.dom.createEl('div', {
className: 'vjs-visual-search-results-wrapper'
className: 'vjs-visual-search-results-wrapper',
role: 'presentation'
});

// Add markers for each result
results.forEach(result => {
const { start_time, end_time } = result;
const position = (start_time / total) * 100;
const width = ((end_time - start_time) / total) * 100;
const time = `${Math.floor(start_time / 60)}:${Math.floor(start_time % 60)
.toString()
.padStart(2, '0')}`;

const markerEl = videojs.dom.createEl('div', {
className: 'vjs-visual-search-marker',
style: `left: ${(start_time / total) * 100}%; width: ${((end_time - start_time) / total) * 100}%`
className: 'vjs-control vjs-visual-search-marker',
style: `left: ${position}%; width: ${width}%`,
tabIndex: 0,
role: 'button',
title: `Search result at ${time}`,
ariaLabel: `Search result at ${time}`
});

wrapperEl.appendChild(markerEl);

// Add click handler to jump to this time
markerEl.addEventListener('click', () => {
player.currentTime(start_time);
});

// Add keyboard support
markerEl.addEventListener('keydown', e => {
if (e.key === 'Enter' || e.key === ' ') {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

space?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

We could do e.code === 'Space' which is more semantically clear, but AFAIU for assistive technology devices that aren't standard keyboards, e.key will give better support because it represents the semantic meaning of the input ("space") and not necessarily the space-key itself.

e.preventDefault();
player.currentTime(start_time);
}
});
});

// Add wrapper to seek bar
seekBar.el().appendChild(wrapperEl);

// Add a class to indicate search results are displayed
if (results.length > 0) {
player.addClass('vjs-visual-search-results-active');
}
};

return {
displayResults,
clearMarkers
Expand Down
4 changes: 4 additions & 0 deletions src/plugins/visual-search/visual-search.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ const visualSearch = (options, player) => {
if (!isSearchActive) {
isSearchActive = true;
searchContainer.classList.add('vjs-visual-search-active');
searchInput.input.tabIndex = 0;
searchInput.closeButton.tabIndex = 0;
searchInput.input.focus();
} else {
const query = searchInput.input.value.trim();
Expand All @@ -88,6 +90,8 @@ const visualSearch = (options, player) => {
isSearchActive = false;
searchContainer.classList.remove('vjs-visual-search-active');
searchInput.input.value = '';
searchInput.input.tabIndex = -1;
searchInput.closeButton.tabIndex = -1;

searchResults.clearMarkers();
}
Expand Down
4 changes: 4 additions & 0 deletions src/plugins/visual-search/visual-search.scss
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@
&:hover {
opacity: 0.8;
}

&:focus {
box-shadow: inset 0 0 0 0.1em color-mix(in srgb, var(--color-text) 50%, transparent);
}
}
}

Expand Down