Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 4 additions & 0 deletions packages/core/src/core/media/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,15 @@ export interface MediaTextTrackCapability {
}

export interface MediaFullscreenCapability {
readonly isFullscreen: boolean;
requestFullscreen(): Promise<void>;
exitFullscreen(): Promise<void>;
}

export interface MediaPictureInPictureCapability {
readonly isPictureInPicture: boolean;
requestPictureInPicture(): Promise<unknown>;
exitPictureInPicture(): Promise<void>;
}

export interface MediaRemotePlaybackCapability {
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/dom/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from './hotkey/actions';
export * from './hotkey/aria';
export * from './hotkey/coordinator';
export * from './hotkey/hotkey';
export * from './media/to-media-host';
export * from './media/types';
export * from './store/features';
export * from './store/selectors';
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/dom/media/predicate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
MediaBufferCapability,
MediaErrorCapability,
MediaPauseCapability,
MediaPictureInPictureCapability,
MediaPlaybackRateCapability,
MediaRemotePlaybackCapability,
MediaSeekCapability,
Expand Down Expand Up @@ -61,6 +62,10 @@ export function isMediaRemotePlaybackCapable(value: unknown): value is MediaRemo
return isObject(value) && 'remote' in value && isObject((value as Record<string, unknown>).remote);
}

export function isMediaPictureInPictureCapable(value: unknown): value is MediaPictureInPictureCapability {
return isObject(value) && 'isPictureInPicture' in value;
}

export function isMediaStreamTypeCapable(value: unknown): value is MediaStreamTypeCapability {
return isObject(value) && 'streamType' in value;
}
Expand Down
71 changes: 71 additions & 0 deletions packages/core/src/dom/media/tests/to-media-host.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { describe, expect, it } from 'vitest';

import { HTMLAudioElementHost } from '../audio-host';
import { toMediaHost } from '../to-media-host';
import { HTMLVideoElementHost } from '../video-host';

describe('toMediaHost', () => {
it('wraps a raw HTMLVideoElement in an HTMLVideoElementHost attached to it', () => {
const video = document.createElement('video');

const result = toMediaHost(video);

expect(result.media).toBeInstanceOf(HTMLVideoElementHost);
expect((result.media as HTMLVideoElementHost).target).toBe(video);
});

it('wraps a raw HTMLAudioElement in an HTMLAudioElementHost attached to it', () => {
const audio = document.createElement('audio');

const result = toMediaHost(audio);

expect(result.media).toBeInstanceOf(HTMLAudioElementHost);
expect((result.media as HTMLAudioElementHost).target).toBe(audio);
});

it('exposes the wrapped host API on the result media (e.g. requestFullscreen)', () => {
const video = document.createElement('video');

const { media } = toMediaHost(video);

expect(typeof media.isFullscreen).toBe('boolean');
expect(typeof media.requestFullscreen).toBe('function');
expect(typeof media.exitFullscreen).toBe('function');
expect(typeof media.isPictureInPicture).toBe('boolean');
expect(typeof media.requestPictureInPicture).toBe('function');
expect(typeof media.exitPictureInPicture).toBe('function');
});

it('release() detaches the auto-created host from the underlying element', () => {
const video = document.createElement('video');
const result = toMediaHost(video);

expect((result.media as HTMLVideoElementHost).target).toBe(video);

result.release();

expect((result.media as HTMLVideoElementHost).target).toBeNull();
});

it('returns existing hosts unchanged with a no-op release', () => {
const video = document.createElement('video');
const host = new HTMLVideoElementHost();
host.attach(video);

const result = toMediaHost(host);

expect(result.media).toBe(host);
result.release();
// Host stays attached — release is a no-op for inputs we did not create.
expect(host.target).toBe(video);
});

it('returns non-element values unchanged with a no-op release', () => {
const fake = { play: () => Promise.resolve() } as unknown as HTMLVideoElement;

const result = toMediaHost(fake);

expect(result.media).toBe(fake);
expect(() => result.release()).not.toThrow();
});
});
Loading
Loading