From 453f86a448a330910614211c3d4cf35b06348b13 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 29 May 2026 14:54:42 +0200 Subject: [PATCH] Implement audio and rendition selection on web --- example/App.tsx | 2 +- package.json | 1 + src/player.web.tsx | 48 +++++++++++++++++++++++++++++++++++++++------- src/view.web.tsx | 1 + 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/example/App.tsx b/example/App.tsx index 385dd3d..08eb255 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -381,7 +381,7 @@ function PlayerExample({ isAutoQuality && styles.selectedTrackButtonText, ]} > - Auto + Auto{isAutoQuality && tracks.renditions.find(r => r.selected) ? ` (${tracks.renditions.find(r => r.selected)!.height}p)` : ""} {tracks.renditions.length === 0 ? ( diff --git a/package.json b/package.json index 6328f42..cc8dbfd 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "scripts": { "dev": "cd example && bun start", "android": " cd example && bunx react-native run-android --no-packager --port 8082", + "web": "cd example && bun web", "typecheck": "tsc --noEmit && tsc -p plugin/tsconfig.json --noEmit", "build:plugin": "tsc -p plugin/tsconfig.json", "build": "bun run typecheck && bob build && bun run build:plugin", diff --git a/src/player.web.tsx b/src/player.web.tsx index a8ac046..ddf2d89 100644 --- a/src/player.web.tsx +++ b/src/player.web.tsx @@ -1,4 +1,5 @@ import type { VideoPlayerStore } from "@videojs/core/dom"; +import type Hls from "hls.js"; import { stateMapper } from "./events.web"; import type { OmniPlayer, @@ -17,6 +18,11 @@ export class WebOmniPlayer implements OmniPlayer { this._store = store; } + private getHls(): Hls | null { + const media = this._store.target?.media; + return (media?.engine as Hls | undefined) ?? null; + } + source: Source | null = null; get showNotification(): boolean { @@ -78,7 +84,9 @@ export class WebOmniPlayer implements OmniPlayer { } get isAutoQuality(): boolean { - return true; + const hls = this.getHls(); + if (!hls) return false; + return hls.autoLevelEnabled; } play(): void { @@ -112,16 +120,31 @@ export class WebOmniPlayer implements OmniPlayer { } get videos(): Track[] { + // hls.js does not support alternative video tracks (e.g. camera angles) return []; } - selectVideo(video: Track): void {} + selectVideo(_video: Track): void { + // hls.js does not support alternative video tracks + } get audios(): Track[] { - return []; + const hls = this.getHls(); + if (!hls) return []; + const currentTrackId = hls.audioTrack; + return hls.audioTracks.map((track, index) => ({ + id: index.toString(), + label: track.name, + language: track.lang, + selected: index === currentTrackId, + })); } - selectAudio(audio: Track): void {} + selectAudio(audio: Track): void { + const hls = this.getHls(); + if (!hls) return; + hls.audioTrack = parseInt(audio.id, 10); + } get subtitles(): Track[] { return this._store.textTrackList @@ -145,10 +168,21 @@ export class WebOmniPlayer implements OmniPlayer { } get rendition(): Rendition[] { - return []; + const hls = this.getHls(); + if (!hls) return []; + const currentLevel = hls.currentLevel; + return hls.levels.map((level, index) => ({ + id: index.toString(), + width: level.width, + height: level.height, + bitrate: level.bitrate, + selected: index === currentLevel, + })); } - selectRendition(_rendition?: Rendition): void { - // Auto quality is handled by videojs/HLS + selectRendition(rendition?: Rendition): void { + const hls = this.getHls(); + if (!hls) return; + hls.nextLevel = rendition ? parseInt(rendition.id, 10) : -1; } } diff --git a/src/view.web.tsx b/src/view.web.tsx index cf0e123..0353f62 100644 --- a/src/view.web.tsx +++ b/src/view.web.tsx @@ -24,6 +24,7 @@ export const OmniView = ({ autoPlay={autoplay} playsInline crossOrigin="anonymous" + style={{ width: "100%", height: "100%", objectFit: "cover" }} /> )}