Skip to content

Commit 19d568f

Browse files
ACCOUNT-30901 add support for tracks in video component (#1540)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent c3fce65 commit 19d568f

6 files changed

Lines changed: 108 additions & 2 deletions

File tree

src/__stories__/story.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ declare type StoryComponent<T = {children?: ReactNode}> = {(props: T): JSX.Eleme
99
declare module '*.jpg';
1010
declare module '*.png';
1111
declare module '*.mp4';
12+
declare module '*.vtt';

src/__stories__/video-story.tsx

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import * as React from 'react';
22
import {Stack, ButtonPrimary, Inline, Video, Text3, Title3} from '..';
33
import beachVideo from './videos/beach.mp4';
4+
import subtitlesExampleEn from './videos/test-subtitles-en.vtt';
5+
import subtitlesExampleEs from './videos/test-subtitles-es.vtt';
46
import beachImg from './images/beach.jpg';
57

68
import type {AspectRatio} from '../video';
@@ -40,6 +42,8 @@ export default {
4042

4143
const VIDEO_SRC = beachVideo;
4244
const POSTER_SRC = beachImg;
45+
const SUBTITLES_SRC_EN = subtitlesExampleEn;
46+
const SUBTITLES_SRC_ES = subtitlesExampleEs;
4347

4448
type Args = {
4549
type: 'width and height' | 'width and aspect ratio' | 'full width';
@@ -60,8 +64,20 @@ export const Default: StoryComponent<Args> = ({
6064
poster,
6165
emptySource,
6266
}) => {
67+
const [enSubtitlesVisible, setEnSubtitlesVisible] = React.useState(true);
68+
const [esSubtitlesVisible, setEsSubtitlesVisible] = React.useState(false);
6369
const videoRef = React.useRef<VideoElement>(null);
6470

71+
const toggleSubtitles = (index: number) => {
72+
const next = index === 0 ? !enSubtitlesVisible : !esSubtitlesVisible;
73+
videoRef.current?.setTrackMode(index, next ? 'showing' : 'disabled');
74+
if (index === 0) {
75+
setEnSubtitlesVisible(next);
76+
} else {
77+
setEsSubtitlesVisible(next);
78+
}
79+
};
80+
6581
const props = {
6682
width: type !== 'full width' ? width : undefined,
6783
height: type === 'width and height' ? height : undefined,
@@ -76,7 +92,28 @@ export const Default: StoryComponent<Args> = ({
7692
dataAttributes: {testid: 'video'},
7793
};
7894

79-
const video = <Video src={!emptySource ? VIDEO_SRC : ''} {...props} ref={videoRef} />;
95+
const video = (
96+
<Video
97+
src={!emptySource ? VIDEO_SRC : ''}
98+
{...props}
99+
ref={videoRef}
100+
tracks={[
101+
{
102+
src: SUBTITLES_SRC_EN,
103+
kind: 'subtitles',
104+
srcLang: 'en',
105+
label: 'English',
106+
default: true,
107+
},
108+
{
109+
src: SUBTITLES_SRC_ES,
110+
kind: 'subtitles',
111+
srcLang: 'es',
112+
label: 'Spanish',
113+
},
114+
]}
115+
/>
116+
);
80117

81118
return (
82119
<Stack space={32}>
@@ -123,6 +160,12 @@ export const Default: StoryComponent<Args> = ({
123160
>
124161
Stop
125162
</ButtonPrimary>
163+
<ButtonPrimary small onPress={() => toggleSubtitles(0)}>
164+
{enSubtitlesVisible ? 'Hide EN subtitles' : 'Show EN subtitles'}
165+
</ButtonPrimary>
166+
<ButtonPrimary small onPress={() => toggleSubtitles(1)}>
167+
{esSubtitlesVisible ? 'Hide ES subtitles' : 'Show ES subtitles'}
168+
</ButtonPrimary>
126169
</Inline>
127170

128171
{video}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
WEBVTT
2+
3+
00:00:00.000 --> 00:00:03.000
4+
This is the first subtitle
5+
6+
00:00:03.000 --> 00:00:06.000
7+
This is the second subtitle
8+
9+
00:00:06.000 --> 00:00:09.000
10+
This is the third subtitle
11+
12+
00:00:09.000 --> 00:00:12.000
13+
Testing subtitles
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
WEBVTT
2+
3+
00:00:00.000 --> 00:00:03.000
4+
Este es el primer subtitulo
5+
6+
00:00:03.000 --> 00:00:06.000
7+
Este es el segundo subtitulo
8+
9+
00:00:06.000 --> 00:00:09.000
10+
Este es el tercer subtitulo
11+
12+
00:00:09.000 --> 00:00:12.000
13+
Testeando subtitulos

src/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ export {
127127
export {default as Image} from './image';
128128
export {default as Chip} from './chip';
129129
export {default as Video} from './video';
130-
export type {VideoElement} from './video';
130+
export type {VideoElement, VideoTrack} from './video';
131131
export {
132132
Carousel,
133133
CenteredCarousel,

src/video.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,15 @@ type VideoSourceWithType = {
7272
type?: string; // video/webm, video/mp4...
7373
};
7474

75+
export type VideoTrack = {
76+
src: string;
77+
kind: 'subtitles' | 'captions';
78+
/** https://developer.mozilla.org/en-US/docs/Glossary/BCP_47_language_tag */
79+
srcLang: string;
80+
label?: string;
81+
default?: boolean;
82+
};
83+
7584
export type VideoSource =
7685
| string
7786
| ReadonlyArray<string>
@@ -103,6 +112,8 @@ export type VideoProps = {
103112
onPause?: () => void;
104113
onLoad?: () => void;
105114
poster?: string;
115+
/** text track elements for subtitles and captions */
116+
tracks?: ReadonlyArray<VideoTrack>;
106117
children?: void;
107118
/** defaults to none */
108119
preload?: 'none' | 'metadata' | 'auto';
@@ -116,13 +127,20 @@ export interface VideoElement extends HTMLDivElement {
116127
/** Stops the video and shows the poster image (if available) */
117128
stop: () => void;
118129
setCurrentTime: (time: number) => void;
130+
/**
131+
* Sets the display mode of a track by its index.
132+
* - 'showing': track is visible
133+
* - 'disabled': track is inactive
134+
*/
135+
setTrackMode: (index: number, mode: 'showing' | 'disabled') => void;
119136
}
120137

121138
const Video = React.forwardRef<VideoElement, VideoProps>(
122139
(
123140
{
124141
src,
125142
poster,
143+
tracks,
126144
autoPlay = 'when-loaded',
127145
muted = true,
128146
loop = true,
@@ -210,6 +228,7 @@ const Video = React.forwardRef<VideoElement, VideoProps>(
210228
disableRemotePlayback
211229
muted={muted}
212230
loop={loop}
231+
crossOrigin={tracks?.length ? 'anonymous' : undefined}
213232
className={classNames(styles.video, mediaStyles.defaultBorderRadius)}
214233
preload={preload}
215234
onError={handleError}
@@ -242,6 +261,16 @@ const Video = React.forwardRef<VideoElement, VideoProps>(
242261
{sources.map(({src, type}, index) => (
243262
<source key={index} src={src} type={type} />
244263
))}
264+
{tracks?.map(({src, kind, srcLang, label, default: isDefault}, index) => (
265+
<track
266+
key={index}
267+
src={src}
268+
kind={kind}
269+
srcLang={srcLang}
270+
label={label}
271+
default={isDefault}
272+
/>
273+
))}
245274
</video>
246275
);
247276

@@ -326,6 +355,13 @@ const Video = React.forwardRef<VideoElement, VideoProps>(
326355
dispatch('stop');
327356
}
328357
};
358+
containerElement.setTrackMode = (index: number, mode: 'showing' | 'disabled') => {
359+
const trackElements = videoRef.current?.querySelectorAll('track');
360+
const trackElement = trackElements?.[index] as HTMLTrackElement | undefined;
361+
if (trackElement?.track) {
362+
trackElement.track.mode = mode;
363+
}
364+
};
329365
}
330366

331367
if (typeof ref === 'function') {

0 commit comments

Comments
 (0)