Skip to content

Commit d08d630

Browse files
committed
feat(player): google cast support
1 parent 6f9c16b commit d08d630

102 files changed

Lines changed: 3558 additions & 493 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ packages/vidstack/player/styles/default/theme.css
3737
packages/react/src/icons.ts
3838
packages/react/index.d.ts
3939
packages/react/icons.d.ts
40+
packages/react/dom.d.ts
41+
packages/react/google-cast.d.ts
4042
packages/react/player/
4143
packages/react/tailwind*
4244

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"clean": "turbo run clean",
1515
"format": "turbo run format --parallel",
1616
"test": "turbo run test --parallel",
17-
"preinstall": "npx only-allow pnpm && pnpx simple-git-hooks",
17+
"preinstall": "npx only-allow pnpm",
1818
"release": "pnpm build && node .scripts/release.js --next",
1919
"release:dry": "pnpm run release --dry",
2020
"validate": "turbo run build format test"

packages/react/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@
9191
},
9292
"./player/styles/*": "./player/styles/*",
9393
"./dist/types/*": "./dist/types/*",
94+
"./dom.d.ts": "./dom.d.ts",
95+
"./google-cast.d.ts": "./google-cast.d.ts",
9496
"./package.json": "./package.json",
9597
"./tailwind.cjs": {
9698
"types": "./tailwind.d.cts",

packages/react/rollup.config.js

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const EXTERNAL_PACKAGES = [
3232
/^remotion/,
3333
],
3434
NPM_BUNDLES = [define({ dev: true }), define({ dev: false })],
35-
TYPES_BUNDLES = [defineTypes()];
35+
TYPES_BUNDLES = defineTypes();
3636

3737
// Styles
3838
if (!MODE_TYPES) {
@@ -53,38 +53,60 @@ export default defineConfig(
5353
);
5454

5555
/**
56-
* @returns {import('rollup').RollupOptions}
56+
* @returns {import('rollup').RollupOptions[]}
5757
* */
5858
function defineTypes() {
59-
return {
60-
input: {
61-
index: 'types/react/src/index.d.ts',
62-
icons: 'types/react/src/icons.d.ts',
63-
'player/remotion': 'types/react/src/providers/remotion/index.d.ts',
64-
'player/layouts/default': 'types/react/src/components/layouts/default/index.d.ts',
65-
},
66-
output: {
67-
dir: '.',
68-
chunkFileNames: 'dist/types/[name].d.ts',
69-
manualChunks(id) {
70-
if (id.includes('react/src')) return 'vidstack-react';
71-
if (id.includes('maverick')) return 'vidstack-framework';
72-
if (id.includes('vidstack')) return 'vidstack';
59+
return [
60+
{
61+
input: {
62+
index: 'types/react/src/index.d.ts',
63+
icons: 'types/react/src/icons.d.ts',
64+
'player/remotion': 'types/react/src/providers/remotion/index.d.ts',
65+
'player/layouts/default': 'types/react/src/components/layouts/default/index.d.ts',
7366
},
74-
},
75-
external: EXTERNAL_PACKAGES,
76-
plugins: [
77-
{
78-
name: 'resolve-vidstack-types',
79-
resolveId(id) {
80-
if (id === 'vidstack') {
81-
return 'types/vidstack/src/index.d.ts';
82-
}
67+
output: {
68+
dir: '.',
69+
chunkFileNames: 'dist/types/[name].d.ts',
70+
manualChunks(id) {
71+
if (id.includes('react/src')) return 'vidstack-react';
72+
if (id.includes('maverick')) return 'vidstack-framework';
73+
if (id.includes('vidstack')) return 'vidstack';
8374
},
8475
},
85-
dts({ respectExternal: true }),
86-
],
87-
};
76+
external: EXTERNAL_PACKAGES,
77+
plugins: [
78+
{
79+
name: 'resolve-vidstack-types',
80+
resolveId(id) {
81+
if (id === 'vidstack') {
82+
return 'types/vidstack/src/index.d.ts';
83+
}
84+
},
85+
},
86+
dts({
87+
respectExternal: true,
88+
}),
89+
{
90+
name: 'globals',
91+
generateBundle(_, bundle) {
92+
const indexFile = Object.values(bundle).find((file) => file.fileName === 'index.d.ts'),
93+
globalFiles = ['dom.d.ts', 'google-cast.d.ts'],
94+
references = globalFiles
95+
.map((path) => `/// <reference path="./${path}" />`)
96+
.join('\n');
97+
98+
for (const file of globalFiles) {
99+
fs.copyFileSync(path.resolve(`../vidstack/${file}`), file);
100+
}
101+
102+
if (indexFile?.type === 'chunk') {
103+
indexFile.code = references + `\n\n${indexFile.code}`;
104+
}
105+
},
106+
},
107+
],
108+
},
109+
];
88110
}
89111

90112
/**

packages/react/src/components/layouts/default/context.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,21 @@ import type { DefaultLayoutIcons } from './icons';
77
export const DefaultLayoutContext = React.createContext<DefaultLayoutContext>({} as any);
88

99
interface DefaultLayoutContext {
10-
thumbnails: ThumbnailSrc;
11-
menuContainer?: React.RefObject<HTMLElement | null>;
12-
translations?: DefaultLayoutTranslations | null;
13-
isSmallLayout: boolean;
14-
showMenuDelay?: number;
15-
showTooltipDelay: number;
10+
disableTimeSlider: boolean;
1611
hideQualityBitrate: boolean;
17-
menuGroup: 'top' | 'bottom';
18-
noModal: boolean;
1912
Icons: DefaultLayoutIcons;
20-
slots?: unknown;
21-
sliderChaptersMinWidth: number;
22-
disableTimeSlider: boolean;
13+
isSmallLayout: boolean;
14+
menuContainer?: React.RefObject<HTMLElement | null>;
15+
menuGroup: 'top' | 'bottom';
2316
noGestures: boolean;
2417
noKeyboardActionDisplay: boolean;
18+
noModal: boolean;
19+
showMenuDelay?: number;
20+
showTooltipDelay: number;
21+
sliderChaptersMinWidth: number;
22+
slots?: unknown;
23+
thumbnails: ThumbnailSrc;
24+
translations?: DefaultLayoutTranslations | null;
2525
}
2626

2727
export function useDefaultLayoutLang(word: keyof DefaultLayoutTranslations) {

packages/react/src/components/layouts/default/icons.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import airPlayPaths from 'media-icons/dist/icons/airplay.js';
44
import arrowLeftPaths from 'media-icons/dist/icons/arrow-left.js';
55
import chaptersIconPaths from 'media-icons/dist/icons/chapters.js';
66
import arrowRightPaths from 'media-icons/dist/icons/chevron-right.js';
7+
import googleCastPaths from 'media-icons/dist/icons/chromecast.js';
78
import ccOnIconPaths from 'media-icons/dist/icons/closed-captions-on.js';
89
import ccIconPaths from 'media-icons/dist/icons/closed-captions.js';
910
import exitFullscreenIconPaths from 'media-icons/dist/icons/fullscreen-exit.js';
@@ -38,6 +39,9 @@ export const defaultLayoutIcons: DefaultLayoutIcons = {
3839
AirPlayButton: {
3940
Default: createIcon(airPlayPaths),
4041
},
42+
GoogleCastButton: {
43+
Default: createIcon(googleCastPaths),
44+
},
4145
PlayButton: {
4246
Play: createIcon(playIconPaths),
4347
Pause: createIcon(pauseIconPaths),
@@ -102,6 +106,11 @@ export interface DefaultLayoutIcons {
102106
Connecting?: DefaultLayoutIcon;
103107
Connected?: DefaultLayoutIcon;
104108
};
109+
GoogleCastButton: {
110+
Default: DefaultLayoutIcon;
111+
Connecting?: DefaultLayoutIcon;
112+
Connected?: DefaultLayoutIcon;
113+
};
105114
PlayButton: {
106115
Play: DefaultLayoutIcon;
107116
Pause: DefaultLayoutIcon;

packages/react/src/components/layouts/default/shared-layout.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import type { PrimitivePropsWithRef } from '../../primitives/nodes';
2323
import { AirPlayButton } from '../../ui/buttons/airplay-button';
2424
import { CaptionButton } from '../../ui/buttons/caption-button';
2525
import { FullscreenButton } from '../../ui/buttons/fullscreen-button';
26+
import { GoogleCastButton } from '../../ui/buttons/google-cast-button';
2627
import { LiveButton } from '../../ui/buttons/live-button';
2728
import { MuteButton } from '../../ui/buttons/mute-button';
2829
import { PIPButton } from '../../ui/buttons/pip-button';
@@ -296,6 +297,34 @@ function DefaultAirPlayButton({ tooltip }: DefaultMediaButtonProps) {
296297
DefaultAirPlayButton.displayName = 'DefaultAirPlayButton';
297298
export { DefaultAirPlayButton };
298299

300+
/* -------------------------------------------------------------------------------------------------
301+
* DefaultGoogleCastButton
302+
* -----------------------------------------------------------------------------------------------*/
303+
304+
function DefaultGoogleCastButton({ tooltip }: DefaultMediaButtonProps) {
305+
const { Icons } = React.useContext(DefaultLayoutContext),
306+
googleCastText = useDefaultLayoutLang('Google Cast'),
307+
state = useMediaState('remotePlaybackState'),
308+
stateText = useDefaultLayoutLang(uppercaseFirstChar(state) as Capitalize<RemotePlaybackState>),
309+
label = `${googleCastText} ${stateText}`,
310+
Icon =
311+
(state === 'connecting'
312+
? Icons.GoogleCastButton.Connecting
313+
: state === 'connected'
314+
? Icons.GoogleCastButton.Connected
315+
: null) ?? Icons.GoogleCastButton.Default;
316+
return (
317+
<DefaultTooltip content={googleCastText} placement={tooltip}>
318+
<GoogleCastButton className="vds-google-cast-button vds-button" aria-label={label}>
319+
{React.createElement(Icon, { className: 'vds-icon' })}
320+
</GoogleCastButton>
321+
</DefaultTooltip>
322+
);
323+
}
324+
325+
DefaultGoogleCastButton.displayName = 'DefaultGoogleCastButton';
326+
export { DefaultGoogleCastButton };
327+
299328
/* -------------------------------------------------------------------------------------------------
300329
* DefaultPlayButton
301330
* -----------------------------------------------------------------------------------------------*/

packages/react/src/components/layouts/default/slots.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export type DefaultLayoutSlotName =
2626
| 'muteButton'
2727
| 'pipButton'
2828
| 'airPlayButton'
29+
| 'googleCastButton'
2930
| 'playButton'
3031
| 'loadButton'
3132
| 'seekBackwardButton'

packages/react/src/components/layouts/default/video-layout.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
DefaultChaptersMenu,
1616
DefaultChapterTitle,
1717
DefaultFullscreenButton,
18+
DefaultGoogleCastButton,
1819
DefaultMuteButton,
1920
DefaultPIPButton,
2021
DefaultPlayButton,
@@ -104,6 +105,7 @@ function DefaultVideoLargeLayout() {
104105
{slot(slots, 'captionButton', <DefaultCaptionButton tooltip="top" />)}
105106
{menuGroup === 'bottom' && <DefaultVideoMenus slots={slots} />}
106107
{slot(slots, 'airPlayButton', <DefaultAirPlayButton tooltip="top" />)}
108+
{slot(slots, 'googleCastButton', <DefaultGoogleCastButton tooltip="top" />)}
107109
{slot(slots, 'pipButton', <DefaultPIPButton tooltip="top" />)}
108110
{slot(slots, 'fullscreenButton', <DefaultFullscreenButton tooltip="top end" />)}
109111
</Controls.Group>
@@ -129,6 +131,7 @@ function DefaultVideoSmallLayout() {
129131
<Controls.Root className="vds-controls">
130132
<Controls.Group className="vds-controls-group">
131133
{slot(slots, 'airPlayButton', <DefaultAirPlayButton tooltip="top start" />)}
134+
{slot(slots, 'googleCastButton', <DefaultGoogleCastButton tooltip="top start" />)}
132135
<div className="vds-controls-spacer" />
133136
{slot(slots, 'captionButton', <DefaultCaptionButton tooltip="bottom" />)}
134137
<DefaultVideoMenus slots={slots} />

packages/react/src/components/primitives/instances.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
ControlsGroup,
77
FullscreenButton,
88
Gesture,
9+
GoogleCastButton,
910
LiveButton,
1011
MediaPlayer,
1112
MediaProvider,
@@ -52,6 +53,7 @@ export class MuteButtonInstance extends MuteButton {}
5253
export class PIPButtonInstance extends PIPButton {}
5354
export class PlayButtonInstance extends PlayButton {}
5455
export class AirPlayButtonInstance extends AirPlayButton {}
56+
export class GoogleCastButtonInstance extends GoogleCastButton {}
5557
export class SeekButtonInstance extends SeekButton {}
5658
// Tooltip
5759
export class TooltipInstance extends Tooltip {}

0 commit comments

Comments
 (0)