forked from muxinc/elements
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(playback-core): custom cap level controller (muxinc#1010)
Implements a custom `CapLevelController` that ensures we never cap to 480p (portrait or landscape) **To test:** 1. Go to: https://elements-demo-nextjs-git-fork-cjpillsbury-feat-custo-7d5867-mux.vercel.app/MuxPlayer 2. In the config UI, use the `width` input to set `MuxPlayer`'s `width` to something smaller than 480p (keep in mind dpp, so you may want to choose something small like `200` 3. In the config UI, use the `playbackId` input to set the desired `playbackId` (or choose one from the dropdown) 4. Confirm that quality doesn't look sad - **NOTE:** For advanced/programmatic confirmation, you can e.g. open the browser's dev tools beforehand, go to the network tab, filter by .m3u8, and make sure the video media playlists being requested match a resolution > 480p from the response content of the multivariant playlist
- Loading branch information
1 parent
192aa79
commit e49e231
Showing
3 changed files
with
69 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import type Hls from 'hls.js'; | ||
import type { Level } from 'hls.js'; | ||
import { CapLevelController } from 'hls.js'; | ||
|
||
/** | ||
* A custom HLS.js CapLevelController that behaves like the default one, except | ||
* it enforces a "minimum maximum" to avoid forced capping to lower quality at small sizes | ||
*/ | ||
class MinCapLevelController extends CapLevelController { | ||
// Never cap below this level. | ||
static minMaxResolution = 720; | ||
|
||
constructor(hls: Hls) { | ||
super(hls); | ||
} | ||
|
||
get levels() { | ||
// NOTE: hls is a TS-private member in CapLevelController. Should be TS-protected (CJP) | ||
// @ts-ignore | ||
return (this.hls.levels ?? []) as Level[]; | ||
} | ||
|
||
getValidLevels(capLevelIndex: number) { | ||
return this.levels.filter( | ||
// NOTE: isLevelAllowed is a TS-private member in CapLevelController. Should be TS-protected (CJP) | ||
// @ts-ignore | ||
(level, index) => this.isLevelAllowed(level) && index <= capLevelIndex | ||
); | ||
} | ||
|
||
getMaxLevel(capLevelIndex: number) { | ||
const baseMaxLevel = super.getMaxLevel(capLevelIndex); | ||
const validLevels = this.getValidLevels(capLevelIndex); | ||
|
||
// Default maxLevel selection ended up out of bounds to indicate e.g. no capping/no levels available (yet), so use it | ||
if (!validLevels[baseMaxLevel]) return baseMaxLevel; | ||
|
||
const baseMaxLevelResolution = Math.min(validLevels[baseMaxLevel].width, validLevels[baseMaxLevel].height); | ||
const preferredMinMaxResolution = MinCapLevelController.minMaxResolution; | ||
|
||
// Default maxLevel selection already meets our conditions, so use it | ||
if (baseMaxLevelResolution >= preferredMinMaxResolution) return baseMaxLevel; | ||
|
||
// Default maxLevel selection is below the preferred "min max", so find the lowest level | ||
// that is >= the preference. We can simply repurpose CapLevelController:getMaxLevelByMediaSize() | ||
// for this, "lying" about the element's size. | ||
// NOTE: Since CapLevelController:getMaxLevelByMediaSize() uses "max square size" under the hood | ||
// already, we don't need to duplicate that logic here. | ||
const maxLevel = CapLevelController.getMaxLevelByMediaSize( | ||
validLevels, | ||
preferredMinMaxResolution * (16 / 9), | ||
preferredMinMaxResolution | ||
); | ||
|
||
return maxLevel; | ||
} | ||
} | ||
|
||
export default MinCapLevelController; |