Skip to content

Commit

Permalink
feat(player): new crossOrigin prop on thumbnail components
Browse files Browse the repository at this point in the history
  • Loading branch information
mihar-22 committed Dec 26, 2023
1 parent d699c7b commit 72b8056
Show file tree
Hide file tree
Showing 10 changed files with 63 additions and 21 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ All notable changes to this project will be documented in this file.
- new `disableTimeSlider` default layout prop ([219b90e](https://github.com/vidstack/player/commit/219b90e643d13ac1cedc1eddea8ae88a67ed994e))
- new `noGestures` default layout prop ([2fff957](https://github.com/vidstack/player/commit/2fff9578490771194a3c6fe0d05a077a88435d0c))
- load chapters from vimeo embed ([8a23415](https://github.com/vidstack/player/commit/8a234151a1251e01cbb62708a6aa2c028eca8f6c))
- new `crossOrigin` prop on poster component ([ecbf277](https://github.com/vidstack/player/commit/ecbf2778e813b357ea60f37c5d81f705979f1083))
- new `crossOrigin` prop on slider video component ([d699c7b](https://github.com/vidstack/player/commit/d699c7b9eb6a583a052f60e562853f7b727f7c5c))
- new `crossOrigin` prop on thumbnail components ([fa9ee2d](https://github.com/vidstack/player/commit/fa9ee2d26465b2c22e081f605849ccfca2a9f102))

#### Player (React)

Expand All @@ -39,6 +42,8 @@ All notable changes to this project will be documented in this file.
- vimeo video info can be undefined (#1062) ([c8b871f](https://github.com/vidstack/player/commit/c8b871f4b00da2a0bac5a2e6bff019d734fdcf0a))
- update icon slots on all mutations ([00073a0](https://github.com/vidstack/player/commit/00073a02fdb884117b228c195df1e4cac35111ff))
- catch false postive vimeo pro detection ([29d6fa0](https://github.com/vidstack/player/commit/29d6fa05fbcc373e47232cf5412f7f1f73446fae))
- use intrisic duration for last vimeo chapter end time ([4dbe21e](https://github.com/vidstack/player/commit/4dbe21eafdba83ef5071f3be7f9eadbc8999e96d))
- rename `crossorigin` prop to `crossOrigin` ([37513ea](https://github.com/vidstack/player/commit/37513ea12c761c65f1dbbfd8280d9635be4ffb50))

#### Player (React)

Expand Down
3 changes: 1 addition & 2 deletions packages/react/src/components/ui/thumbnail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ export interface ImgProps extends PrimitivePropsWithRef<'img'> {
}

const Img = React.forwardRef<HTMLImageElement, ImgProps>(({ children, ...props }, forwardRef) => {
const { crossOrigin } = useStateContext(mediaState),
{ src, img } = useStateContext(ThumbnailInstance.state),
const { src, img, crossOrigin } = useStateContext(ThumbnailInstance.state),
$src = useSignal(src),
$crossOrigin = useSignal(crossOrigin);
return (
Expand Down
14 changes: 11 additions & 3 deletions packages/react/src/hooks/use-thumbnails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as React from 'react';
import { scoped, signal } from 'maverick.js';
import { useReactScope, useSignal } from 'maverick.js/react';
import type { VTTCue } from 'media-captions';
import { findActiveCue, ThumbnailsLoader } from 'vidstack';
import { findActiveCue, ThumbnailsLoader, type MediaCrossOrigin } from 'vidstack';

export interface ThumbnailData {
url: string;
Expand All @@ -21,10 +21,14 @@ export interface ThumbnailData {
*
* @docs {@link https://www.vidstack.io/docs/player/api/hooks/use-thumbnails}
*/
export function useThumbnails(src: string): ThumbnailData[] {
export function useThumbnails(src: string, crossOrigin?: MediaCrossOrigin | null): ThumbnailData[] {
const scope = useReactScope(),
$src = React.useMemo(() => signal(src), []),
loader = React.useMemo(() => scoped(() => ThumbnailsLoader.create($src), scope)!, []),
$crossOrigin = React.useMemo(() => signal($crossOrigin), []),
loader = React.useMemo(
() => scoped(() => ThumbnailsLoader.create($src, $crossOrigin), scope)!,
[],
),
$cues = useSignal(loader.$cues),
data = React.useMemo(() => {
const items: ThumbnailData[] = [],
Expand Down Expand Up @@ -56,6 +60,10 @@ export function useThumbnails(src: string): ThumbnailData[] {
$src.set(src);
}, [src]);

React.useEffect(() => {
$crossOrigin.set(crossOrigin);
}, [crossOrigin]);

return data;
}

Expand Down
5 changes: 3 additions & 2 deletions packages/vidstack/src/components/ui/poster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Component, effect, State } from 'maverick.js';
import { isNull, listenEvent, setAttribute } from 'maverick.js/std';

import { useMediaContext, type MediaContext } from '../../core/api/media-context';
import type { MediaCrossOrigin } from '../../core/api/types';
import { preconnect } from '../../utils/network';

export interface PosterProps {
Expand All @@ -20,14 +21,14 @@ export interface PosterProps {
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin}
*/
crossOrigin: true | '' | 'anonymous' | 'use-credentials' | null;
crossOrigin: true | MediaCrossOrigin | null;
}

export interface PosterState {
img: HTMLImageElement | null;
src: string | null;
alt: string | null;
crossOrigin: '' | 'anonymous' | 'use-credentials' | null;
crossOrigin: MediaCrossOrigin | null;
loading: boolean;
error: ErrorEvent | null;
hidden: boolean;
Expand Down
6 changes: 3 additions & 3 deletions packages/vidstack/src/components/ui/sliders/slider-video.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Component, effect, prop, State, useState, type StateContext } from 'mav
import { isNull, listenEvent, type DOMEvent } from 'maverick.js/std';

import { useMediaContext, type MediaContext } from '../../../core/api/media-context';
import type { MediaCrossOrigin } from '../../../core/api/types';
import { $ariaBool } from '../../../utils/aria';
import { Slider } from './slider/slider';

Expand Down Expand Up @@ -144,20 +145,19 @@ export interface SliderVideoProps {
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/src}
*/
src: string | null;

/**
* Defines how the media handles cross-origin requests, thereby enabling the
* configuration of the CORS requests for the element's fetched data.
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin}
*/
crossOrigin: true | '' | 'anonymous' | 'use-credentials' | null;
crossOrigin: true | MediaCrossOrigin | null;
}

export interface SliderVideoState {
video: HTMLVideoElement | null;
src: string | null;
crossOrigin: '' | 'anonymous' | 'use-credentials' | null;
crossOrigin: MediaCrossOrigin | null;
canPlay: boolean;
error: ErrorEvent | null;
hidden: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { effect, onDispose, peek, signal, type ReadSignal } from 'maverick.js';
import type { VTTCue } from 'media-captions';

import { useMediaContext, type MediaContext } from '../../../core/api/media-context';
import type { MediaCrossOrigin } from '../../../core/api/types';
import { parseJSONCaptionsFile } from '../../../core/tracks/text/text-track';
import { getRequestCredentials } from '../../../utils/network';

Expand All @@ -12,13 +13,14 @@ const cache = new Map<string, VTTCue[]>(),
export class ThumbnailsLoader {
readonly $cues = signal<VTTCue[]>([]);

static create($src: ReadSignal<string>) {
static create($src: ReadSignal<string>, $crossOrigin: ReadSignal<MediaCrossOrigin | null>) {
const media = useMediaContext();
return new ThumbnailsLoader($src, media);
return new ThumbnailsLoader($src, $crossOrigin, media);
}

constructor(
readonly $src: ReadSignal<string>,
readonly $crossOrigin: ReadSignal<MediaCrossOrigin | null>,
private _media: MediaContext,
) {
effect(this._onLoadCues.bind(this));
Expand All @@ -32,8 +34,7 @@ export class ThumbnailsLoader {

if (!canLoad()) return;

const controller = new AbortController(),
{ crossOrigin } = this._media.$state;
const controller = new AbortController();

const src = this.$src();
if (!src) return;
Expand All @@ -56,7 +57,7 @@ export class ThumbnailsLoader {
try {
const response = await fetch(src, {
signal: controller.signal,
credentials: getRequestCredentials(crossOrigin()),
credentials: getRequestCredentials(this.$crossOrigin()),
}),
isJSON = response.headers.get('content-type') === 'application/json';

Expand Down
24 changes: 23 additions & 1 deletion packages/vidstack/src/components/ui/thumbnails/thumbnail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { animationFrameThrottle, isNull, listenEvent } from 'maverick.js/std';
import type { VTTCue } from 'media-captions';

import { useMediaContext, type MediaContext } from '../../../core/api/media-context';
import type { MediaCrossOrigin } from '../../../core/api/types';
import { findActiveCue } from '../../../core/tracks/text/utils';
import { $ariaBool } from '../../../utils/aria';
import { ThumbnailsLoader } from './thumbnail-loader';

export interface ThumbnailState {
src: string;
img: HTMLImageElement | null | undefined;
crossOrigin: MediaCrossOrigin | null;
coords: ThumbnailCoords | null;
activeCue: VTTCue | null;
loading: boolean;
Expand All @@ -29,13 +31,15 @@ export class Thumbnail extends Component<ThumbnailProps, ThumbnailState> {
static props: ThumbnailProps = {
src: '',
time: 0,
crossOrigin: null,
};

static state = new State<ThumbnailState>({
src: '',
img: null,
coords: null,
activeCue: null,
crossOrigin: null,
loading: false,
error: null,
hidden: false,
Expand All @@ -48,7 +52,9 @@ export class Thumbnail extends Component<ThumbnailProps, ThumbnailState> {

protected override onSetup(): void {
this._media = useMediaContext();
this._thumbnails = ThumbnailsLoader.create(this.$props.src);
this._thumbnails = ThumbnailsLoader.create(this.$props.src, this.$state.crossOrigin);

this._watchCrossOrigin();

this.setAttributes({
'data-loading': this._isLoading.bind(this),
Expand All @@ -61,6 +67,7 @@ export class Thumbnail extends Component<ThumbnailProps, ThumbnailState> {
protected override onConnect(el: HTMLElement) {
effect(this._watchImg.bind(this));
effect(this._watchHidden.bind(this));
effect(this._watchCrossOrigin.bind(this));
effect(this._onLoadStart.bind(this));
effect(this._onFindActiveCue.bind(this));
effect(this._onResolveThumbnail.bind(this));
Expand All @@ -73,6 +80,14 @@ export class Thumbnail extends Component<ThumbnailProps, ThumbnailState> {
listenEvent(img, 'error', this._onError.bind(this));
}

private _watchCrossOrigin() {
const { crossOrigin: crossOriginProp } = this.$props,
{ crossOrigin: crossOriginState } = this.$state,
{ crossOrigin: mediaCrossOrigin } = this._media.$state,
crossOrigin = crossOriginProp() !== null ? crossOriginProp() : mediaCrossOrigin();
crossOriginState.set(crossOrigin === true ? 'anonymous' : crossOrigin);
}

private _onLoadStart() {
const { src, loading, error } = this.$state;
src();
Expand Down Expand Up @@ -216,6 +231,13 @@ export interface ThumbnailProps {
* Finds, loads, and displays the first active thumbnail cue that's start/end times are in range.
*/
time: number;
/**
* Defines how the media handles cross-origin requests, thereby enabling the
* configuration of the CORS requests for the element's fetched data.
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin}
*/
crossOrigin: true | MediaCrossOrigin | null;
}

export interface ThumbnailCoords {
Expand Down
3 changes: 2 additions & 1 deletion packages/vidstack/src/core/api/player-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getTimeRangesEnd, getTimeRangesStart, TimeRange } from '../time-ranges'
import type { AudioTrack } from '../tracks/audio-tracks';
import type { TextTrack } from '../tracks/text/text-track';
import type {
MediaCrossOrigin,
MediaErrorDetail,
MediaSrc,
MediaStreamType,
Expand Down Expand Up @@ -359,7 +360,7 @@ export interface MediaState {
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin}
*/
crossOrigin: '' | 'anonymous' | 'use-credentials' | null;
crossOrigin: MediaCrossOrigin | null;
/**
* The URL of the current poster. Defaults to `''` if no media/poster has been given or
* loaded.
Expand Down
2 changes: 2 additions & 0 deletions packages/vidstack/src/core/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export type MediaStreamType =
| 'll-live'
| 'll-live:dvr';

export type MediaCrossOrigin = '' | 'anonymous' | 'use-credentials';

/**
* Indicates the current view type which determines how the media will be presented.
*/
Expand Down
11 changes: 7 additions & 4 deletions packages/vidstack/src/elements/define/thumbnail-element.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { effect } from 'maverick.js';
import { Host } from 'maverick.js/element';
import { Host, type Attributes } from 'maverick.js/element';
import { setAttribute } from 'maverick.js/std';

import { Thumbnail } from '../../components';
import { Thumbnail, type ThumbnailProps } from '../../components';
import { useMediaContext, type MediaContext } from '../../core/api/media-context';
import { cloneTemplateContent, createTemplate } from '../../utils/dom';

Expand All @@ -26,6 +26,10 @@ const imgTemplate = /* #__PURE__*/ createTemplate(
export class MediaThumbnailElement extends Host(HTMLElement, Thumbnail) {
static tagName = 'media-thumbnail';

static override attrs: Attributes<ThumbnailProps> = {
crossOrigin: 'crossorigin',
};

protected _media!: MediaContext;
protected _img = this._createImg();

Expand All @@ -35,8 +39,7 @@ export class MediaThumbnailElement extends Host(HTMLElement, Thumbnail) {
}

protected onConnect(): void {
const { src } = this.$state,
{ crossOrigin } = this._media.$props;
const { src, crossOrigin } = this.$state;

if (this._img.parentNode !== this) {
this.prepend(this._img);
Expand Down

0 comments on commit 72b8056

Please sign in to comment.