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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ So hls.js runs wherever Media Source exists (Chrome, Firefox, Edge, Android, des

- **VOD** — scrub with buffered range, ±10s, speed, resume, chapters, `?t=` timestamps, quality.
- **Live** — none of those, because each is a lie about a stream with no end: nothing to scrub towards, no position worth remembering, no speed but 1. A LIVE badge instead. HLS flips into this from the playlist, after the bar has already been drawn.
- **Audio** — a compact bar in normal flow: no stage, no fullscreen, no picture-in-picture. Still a full transport.
- **Audio** — a compact bar in normal flow: no stage, no fullscreen, no picture-in-picture. Still a full transport. Inferred from an `.mp3`-shaped URL or from an `<audio>` element passed as `media`; pass `audio: true` when the host knows better — a radio station is an `.m3u8` with no picture in it, and would otherwise get a black stage and a LIVE badge.

## Everything else it does

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@profullstack/player",
"version": "0.5.0",
"version": "0.6.0",
"description": "One web player for every source a Profullstack site serves: MP4, HLS, MPEG-2 transport streams and audio, with one control bar, on desktop, mobile, PWA and television.",
"keywords": [
"video",
Expand Down
17 changes: 16 additions & 1 deletion src/core/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ export interface PlayerOptions {
kind?: SourceKind;
/** A server-declared Content-Type, if one was seen. */
mimeType?: string;
/**
* Draw the compact audio bar whatever the source looks like.
*
* The audio shape is normally inferred from the source, and an `.mp3` gets
* it without asking. A radio station is HLS: the URL ends in `.m3u8`, the
* playlist carries nothing but AAC, and the inference draws a black 16:9
* stage with a LIVE badge over a picture that will never arrive. The host
* knows it is audio; this is how it says so. Inferred as well when the
* element handed in through `media` is an `<audio>`.
*/
audio?: boolean;
/**
* Stable key for this recording; what a resume position is filed under. Omit
* and nothing is remembered — which is right for a live stream and for
Expand Down Expand Up @@ -211,7 +222,11 @@ export function createPlayer(root: HTMLElement, options: PlayerOptions): PlayerH
},
caps
);
const audioOnly = isAudioKind(choice.kind);
const audioOnly =
options.audio ??
(typeof HTMLAudioElement !== 'undefined' && options.media instanceof HTMLAudioElement
? true
: isAudioKind(choice.kind));
// A transport stream has no end and no seekable range; HLS tells us later,
// from the playlist, and flips this if it turns out to be a live edge.
let live = options.live ?? choice.kind === 'mpegts';
Expand Down
30 changes: 30 additions & 0 deletions test/player.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,36 @@ describe('createPlayer', () => {
expect(control(root, 'play')?.hasAttribute('hidden')).toBe(false);
expect(control(root, 'rate')?.hasAttribute('hidden')).toBe(false);
});

it('takes the compact bar for an HLS source when the host says it is audio', () => {
// A radio station: the URL is a playlist, the engine is hls.js, and there
// is no picture. Inference alone would build a video stage for it.
const { root, media } = mount({
src: 'https://example.test/radio.m3u8',
audio: true,
capabilities: { mediaSource: true, nativeHls: false },
});
expect(media.tagName).toBe('AUDIO');
expect(root.classList.contains('pux-player--audio')).toBe(true);
expect(control(root, 'fullscreen')?.hasAttribute('hidden')).toBe(true);
expect(control(root, 'pip')?.hasAttribute('hidden')).toBe(true);
});

it('infers audio from an <audio> element it was handed', () => {
const root = document.createElement('div');
const existing = document.createElement('audio');
root.append(existing);
document.body.append(root);
const handle = createPlayer(root, {
src: 'https://example.test/radio.m3u8',
media: existing,
capabilities: { mediaSource: true, nativeHls: false },
engines: { native: fakeEngine(), hls: fakeEngine(), mpegts: fakeEngine() },
});
expect(handle.media).toBe(existing);
expect(root.classList.contains('pux-player--audio')).toBe(true);
handle.destroy();
});
});

describe('quality', () => {
Expand Down
Loading